refactor: adding alias management and extender support 96 pass
This commit is contained in:
parent
786224caf5
commit
972c0424e1
4 changed files with 463 additions and 15 deletions
|
@ -24,6 +24,12 @@ class Container {
|
||||||
final Map<Type, dynamic Function(Container)> _factories = {};
|
final Map<Type, dynamic Function(Container)> _factories = {};
|
||||||
final Map<String, dynamic> _namedSingletons = {};
|
final Map<String, dynamic> _namedSingletons = {};
|
||||||
|
|
||||||
|
/// The container's type aliases
|
||||||
|
final Map<Type, Type> _aliases = {};
|
||||||
|
|
||||||
|
/// The container's service extenders
|
||||||
|
final Map<Type, List<dynamic Function(dynamic, Container)>> _extenders = {};
|
||||||
|
|
||||||
/// The container's contextual bindings
|
/// The container's contextual bindings
|
||||||
final Map<Type, Map<Type, dynamic>> _contextual = {};
|
final Map<Type, Map<Type, dynamic>> _contextual = {};
|
||||||
|
|
||||||
|
@ -135,6 +141,9 @@ class Container {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the type is aliased
|
||||||
|
t2 = getAlias(t2);
|
||||||
|
|
||||||
Container? search = this;
|
Container? search = this;
|
||||||
while (search != null) {
|
while (search != null) {
|
||||||
if (search._singletons.containsKey(t2)) {
|
if (search._singletons.containsKey(t2)) {
|
||||||
|
@ -253,6 +262,7 @@ class Container {
|
||||||
/// This method is central to the dependency injection mechanism, allowing for
|
/// This method is central to the dependency injection mechanism, allowing for
|
||||||
/// flexible object creation and dependency resolution within the container hierarchy.
|
/// flexible object creation and dependency resolution within the container hierarchy.
|
||||||
T make<T>([Type? type]) {
|
T make<T>([Type? type]) {
|
||||||
|
// Get the original type
|
||||||
Type t2 = T;
|
Type t2 = T;
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
t2 = type;
|
t2 = type;
|
||||||
|
@ -324,6 +334,7 @@ class Container {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instance != null) {
|
if (instance != null) {
|
||||||
|
instance = _applyExtenders(t2, instance);
|
||||||
var typedInstance = instance as T;
|
var typedInstance = instance as T;
|
||||||
_fireResolvingCallbacks(typedInstance);
|
_fireResolvingCallbacks(typedInstance);
|
||||||
_fireAfterResolvingCallbacks(typedInstance);
|
_fireAfterResolvingCallbacks(typedInstance);
|
||||||
|
@ -354,6 +365,7 @@ class Container {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (instance != null) {
|
if (instance != null) {
|
||||||
|
instance = _applyExtenders(t2, instance);
|
||||||
var typedInstance = instance as T;
|
var typedInstance = instance as T;
|
||||||
_fireResolvingCallbacks(typedInstance);
|
_fireResolvingCallbacks(typedInstance);
|
||||||
_fireAfterResolvingCallbacks(typedInstance);
|
_fireAfterResolvingCallbacks(typedInstance);
|
||||||
|
@ -361,19 +373,22 @@ class Container {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for singleton or factory
|
// Check for singleton or factory, resolving aliases if no contextual binding was found
|
||||||
Container? search = this;
|
Container? search = this;
|
||||||
|
var resolvedType = contextualConcrete == null ? getAlias(t2) : t2;
|
||||||
while (search != null) {
|
while (search != null) {
|
||||||
if (search._singletons.containsKey(t2)) {
|
if (search._singletons.containsKey(resolvedType)) {
|
||||||
var instance = search._singletons[t2] as T;
|
var instance = search._singletons[resolvedType];
|
||||||
|
instance = _applyExtenders(resolvedType, instance);
|
||||||
_fireResolvingCallbacks(instance);
|
_fireResolvingCallbacks(instance);
|
||||||
_fireAfterResolvingCallbacks(instance);
|
_fireAfterResolvingCallbacks(instance);
|
||||||
return instance;
|
return instance as T;
|
||||||
} else if (search._factories.containsKey(t2)) {
|
} else if (search._factories.containsKey(resolvedType)) {
|
||||||
var instance = search._factories[t2]!(this) as T;
|
var instance = search._factories[resolvedType]!(this);
|
||||||
|
instance = _applyExtenders(resolvedType, instance);
|
||||||
_fireResolvingCallbacks(instance);
|
_fireResolvingCallbacks(instance);
|
||||||
_fireAfterResolvingCallbacks(instance);
|
_fireAfterResolvingCallbacks(instance);
|
||||||
return instance;
|
return instance as T;
|
||||||
} else {
|
} else {
|
||||||
search = search._parent;
|
search = search._parent;
|
||||||
}
|
}
|
||||||
|
@ -443,11 +458,12 @@ class Container {
|
||||||
var instance = reflectedType.newInstance(
|
var instance = reflectedType.newInstance(
|
||||||
isDefault(constructor.name) ? '' : constructor.name,
|
isDefault(constructor.name) ? '' : constructor.name,
|
||||||
positional,
|
positional,
|
||||||
named, []).reflectee as T;
|
named, []).reflectee;
|
||||||
|
|
||||||
|
instance = _applyExtenders(t2, instance);
|
||||||
_fireResolvingCallbacks(instance);
|
_fireResolvingCallbacks(instance);
|
||||||
_fireAfterResolvingCallbacks(instance);
|
_fireAfterResolvingCallbacks(instance);
|
||||||
return instance;
|
return instance as T;
|
||||||
} else {
|
} else {
|
||||||
throw BindingResolutionException(
|
throw BindingResolutionException(
|
||||||
'$t2 is not a class, and therefore cannot be instantiated.');
|
'$t2 is not a class, and therefore cannot be instantiated.');
|
||||||
|
@ -722,8 +738,16 @@ class Container {
|
||||||
while (search != null) {
|
while (search != null) {
|
||||||
var building = _buildStack.last;
|
var building = _buildStack.last;
|
||||||
var contextMap = search._contextual[building];
|
var contextMap = search._contextual[building];
|
||||||
if (contextMap != null && contextMap.containsKey(abstract)) {
|
if (contextMap != null) {
|
||||||
return contextMap[abstract];
|
// First try to find a binding for the original type
|
||||||
|
if (contextMap.containsKey(abstract)) {
|
||||||
|
return contextMap[abstract];
|
||||||
|
}
|
||||||
|
// Then try to find a binding for the aliased type
|
||||||
|
var aliasedType = getAlias(abstract);
|
||||||
|
if (aliasedType != abstract && contextMap.containsKey(aliasedType)) {
|
||||||
|
return contextMap[aliasedType];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
search = search._parent;
|
search = search._parent;
|
||||||
}
|
}
|
||||||
|
@ -768,8 +792,16 @@ class Container {
|
||||||
while (search != null) {
|
while (search != null) {
|
||||||
var building = _buildStack.last;
|
var building = _buildStack.last;
|
||||||
var contextMap = search._contextual[building];
|
var contextMap = search._contextual[building];
|
||||||
if (contextMap != null && contextMap.containsKey(type)) {
|
if (contextMap != null) {
|
||||||
return true;
|
// First check for binding of original type
|
||||||
|
if (contextMap.containsKey(type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Then check for binding of aliased type
|
||||||
|
var aliasedType = getAlias(type);
|
||||||
|
if (aliasedType != type && contextMap.containsKey(aliasedType)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
search = search._parent;
|
search = search._parent;
|
||||||
}
|
}
|
||||||
|
@ -777,6 +809,80 @@ class Container {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register an alias for an abstract type.
|
||||||
|
///
|
||||||
|
/// This allows you to alias an abstract type to a concrete implementation.
|
||||||
|
/// For example, you might alias an interface to its default implementation:
|
||||||
|
/// ```dart
|
||||||
|
/// container.alias<Logger>(ConsoleLogger);
|
||||||
|
/// ```
|
||||||
|
void alias<T>(Type concrete) {
|
||||||
|
_aliases[T] = concrete;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the concrete type that an abstract type is aliased to.
|
||||||
|
///
|
||||||
|
/// If the type is not aliased in this container or any parent container,
|
||||||
|
/// returns the type itself.
|
||||||
|
Type getAlias(Type abstract) {
|
||||||
|
Container? search = this;
|
||||||
|
while (search != null) {
|
||||||
|
if (search._aliases.containsKey(abstract)) {
|
||||||
|
return search._aliases[abstract]!;
|
||||||
|
}
|
||||||
|
search = search._parent;
|
||||||
|
}
|
||||||
|
return abstract;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a type is aliased to another type in this container or any parent container.
|
||||||
|
bool isAlias(Type type) {
|
||||||
|
Container? search = this;
|
||||||
|
while (search != null) {
|
||||||
|
if (search._aliases.containsKey(type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
search = search._parent;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extend a service after it is resolved.
|
||||||
|
///
|
||||||
|
/// This allows you to modify a service after it has been resolved from the container.
|
||||||
|
/// The callback receives the resolved instance and the container, and should return
|
||||||
|
/// the modified instance.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// container.extend<Logger>((logger, container) {
|
||||||
|
/// logger.level = LogLevel.debug;
|
||||||
|
/// return logger;
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
void extend<T>(
|
||||||
|
dynamic Function(dynamic instance, Container container) callback) {
|
||||||
|
_extenders.putIfAbsent(T, () => []).add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply any registered extenders to an instance.
|
||||||
|
dynamic _applyExtenders(Type type, dynamic instance) {
|
||||||
|
// Collect all extenders from parent to child
|
||||||
|
var extenders = <dynamic Function(dynamic, Container)>[];
|
||||||
|
Container? search = this;
|
||||||
|
while (search != null) {
|
||||||
|
if (search._extenders.containsKey(type)) {
|
||||||
|
extenders.insertAll(0, search._extenders[type]!);
|
||||||
|
}
|
||||||
|
search = search._parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply extenders in order (parent to child)
|
||||||
|
for (var extender in extenders) {
|
||||||
|
instance = extender(instance, this);
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if we're in danger of a circular dependency.
|
/// Check if we're in danger of a circular dependency.
|
||||||
void _checkCircularDependency(Type type) {
|
void _checkCircularDependency(Type type) {
|
||||||
if (_buildStack.contains(type)) {
|
if (_buildStack.contains(type)) {
|
||||||
|
|
|
@ -56,8 +56,7 @@ class ContextualImplementationBuilder {
|
||||||
/// Specify the implementation that should be used
|
/// Specify the implementation that should be used
|
||||||
void give<T>() {
|
void give<T>() {
|
||||||
for (var concreteType in concrete) {
|
for (var concreteType in concrete) {
|
||||||
container.addContextualBinding(
|
container.addContextualBinding(concreteType, abstract, T);
|
||||||
concreteType, abstract, (Container c) => c.make<T>());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
230
incubation/container/container/test/alias_test.dart
Normal file
230
incubation/container/container/test/alias_test.dart
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
import 'package:platformed_container/container.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'common.dart';
|
||||||
|
|
||||||
|
// Test interfaces and implementations
|
||||||
|
abstract class Logger {
|
||||||
|
void log(String message);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConsoleLogger implements Logger {
|
||||||
|
@override
|
||||||
|
void log(String message) => print('Console: $message');
|
||||||
|
}
|
||||||
|
|
||||||
|
class FileLogger implements Logger {
|
||||||
|
final String filename;
|
||||||
|
FileLogger(this.filename);
|
||||||
|
@override
|
||||||
|
void log(String message) => print('File($filename): $message');
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoggerClient {
|
||||||
|
final Logger logger;
|
||||||
|
LoggerClient(this.logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockReflector extends Reflector {
|
||||||
|
@override
|
||||||
|
String? getName(Symbol symbol) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedClass? reflectClass(Type clazz) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedType? reflectType(Type type) {
|
||||||
|
if (type == LoggerClient) {
|
||||||
|
return MockReflectedClass(
|
||||||
|
'LoggerClient',
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
MockConstructor([MockParameter('logger', Logger)])
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
type,
|
||||||
|
(name, positional, named, typeArgs) => LoggerClient(positional[0]),
|
||||||
|
);
|
||||||
|
} else if (type == FileLogger) {
|
||||||
|
return MockReflectedClass(
|
||||||
|
'FileLogger',
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
MockConstructor([MockParameter('filename', String)])
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
type,
|
||||||
|
(name, positional, named, typeArgs) => FileLogger(positional[0]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedFunction? reflectFunction(Function function) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockReflectedClass extends ReflectedClass {
|
||||||
|
final Function instanceBuilder;
|
||||||
|
|
||||||
|
MockReflectedClass(
|
||||||
|
String name,
|
||||||
|
List<ReflectedTypeParameter> typeParameters,
|
||||||
|
List<ReflectedInstance> annotations,
|
||||||
|
List<ReflectedFunction> constructors,
|
||||||
|
List<ReflectedDeclaration> declarations,
|
||||||
|
Type reflectedType,
|
||||||
|
this.instanceBuilder,
|
||||||
|
) : super(name, typeParameters, annotations, constructors, declarations,
|
||||||
|
reflectedType);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedInstance newInstance(
|
||||||
|
String constructorName, List positionalArguments,
|
||||||
|
[Map<String, dynamic> namedArguments = const {},
|
||||||
|
List<Type> typeArguments = const []]) {
|
||||||
|
var instance = instanceBuilder(
|
||||||
|
constructorName, positionalArguments, namedArguments, typeArguments);
|
||||||
|
return MockReflectedInstance(this, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isAssignableTo(ReflectedType? other) {
|
||||||
|
if (other == null) return false;
|
||||||
|
return reflectedType == other.reflectedType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockReflectedInstance extends ReflectedInstance {
|
||||||
|
MockReflectedInstance(ReflectedClass clazz, Object? reflectee)
|
||||||
|
: super(clazz, clazz, reflectee);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedInstance getField(String name) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockConstructor extends ReflectedFunction {
|
||||||
|
final List<ReflectedParameter> params;
|
||||||
|
|
||||||
|
MockConstructor(this.params)
|
||||||
|
: super('', [], [], params, false, false,
|
||||||
|
returnType: MockReflectedType('void', [], dynamic));
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedInstance invoke(Invocation invocation) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockParameter extends ReflectedParameter {
|
||||||
|
MockParameter(String name, Type type)
|
||||||
|
: super(name, [], MockReflectedType(type.toString(), [], type), true,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockReflectedType extends ReflectedType {
|
||||||
|
MockReflectedType(String name, List<ReflectedTypeParameter> typeParameters,
|
||||||
|
Type reflectedType)
|
||||||
|
: super(name, typeParameters, reflectedType);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedInstance newInstance(
|
||||||
|
String constructorName, List positionalArguments,
|
||||||
|
[Map<String, dynamic> namedArguments = const {},
|
||||||
|
List<Type> typeArguments = const []]) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isAssignableTo(ReflectedType? other) {
|
||||||
|
if (other == null) return false;
|
||||||
|
return reflectedType == other.reflectedType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late Container container;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
container = Container(MockReflector());
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Alias Tests', () {
|
||||||
|
test('alias resolves to concrete type', () {
|
||||||
|
container.registerSingleton<ConsoleLogger>(ConsoleLogger());
|
||||||
|
container.alias<Logger>(ConsoleLogger);
|
||||||
|
|
||||||
|
var logger = container.make<Logger>();
|
||||||
|
expect(logger, isA<ConsoleLogger>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isAlias returns true for aliased type', () {
|
||||||
|
container.alias<Logger>(ConsoleLogger);
|
||||||
|
expect(container.isAlias(Logger), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isAlias returns false for non-aliased type', () {
|
||||||
|
expect(container.isAlias(ConsoleLogger), isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getAlias returns concrete type for aliased type', () {
|
||||||
|
container.alias<Logger>(ConsoleLogger);
|
||||||
|
expect(container.getAlias(Logger), equals(ConsoleLogger));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getAlias returns same type for non-aliased type', () {
|
||||||
|
expect(container.getAlias(ConsoleLogger), equals(ConsoleLogger));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('alias works with contextual bindings', () {
|
||||||
|
// Register both logger implementations
|
||||||
|
container.registerSingleton<ConsoleLogger>(ConsoleLogger());
|
||||||
|
container.registerSingleton<FileLogger>(FileLogger('test.log'));
|
||||||
|
|
||||||
|
// Set up the alias
|
||||||
|
container.alias<Logger>(ConsoleLogger);
|
||||||
|
|
||||||
|
// Set up contextual binding for the interface
|
||||||
|
container.when(LoggerClient).needs<Logger>().give<FileLogger>();
|
||||||
|
|
||||||
|
var logger = container.make<Logger>();
|
||||||
|
expect(logger, isA<ConsoleLogger>());
|
||||||
|
|
||||||
|
var client = container.make<LoggerClient>();
|
||||||
|
expect(client.logger, isA<FileLogger>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('child container inherits parent aliases', () {
|
||||||
|
container.registerSingleton<ConsoleLogger>(ConsoleLogger());
|
||||||
|
container.alias<Logger>(ConsoleLogger);
|
||||||
|
|
||||||
|
var child = container.createChild();
|
||||||
|
var logger = child.make<Logger>();
|
||||||
|
expect(logger, isA<ConsoleLogger>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('child container can override parent aliases', () {
|
||||||
|
container.registerSingleton<ConsoleLogger>(ConsoleLogger());
|
||||||
|
container.registerSingleton<FileLogger>(FileLogger('test.log'));
|
||||||
|
container.alias<Logger>(ConsoleLogger);
|
||||||
|
|
||||||
|
var child = container.createChild();
|
||||||
|
child.alias<Logger>(FileLogger);
|
||||||
|
|
||||||
|
var parentLogger = container.make<Logger>();
|
||||||
|
expect(parentLogger, isA<ConsoleLogger>());
|
||||||
|
|
||||||
|
var childLogger = child.make<Logger>();
|
||||||
|
expect(childLogger, isA<FileLogger>());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
113
incubation/container/container/test/extend_test.dart
Normal file
113
incubation/container/container/test/extend_test.dart
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
import 'package:platformed_container/container.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
class MockReflector extends Reflector {
|
||||||
|
@override
|
||||||
|
String? getName(Symbol symbol) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedClass? reflectClass(Type clazz) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedType? reflectType(Type type) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedFunction? reflectFunction(Function function) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Logger {
|
||||||
|
LogLevel get level;
|
||||||
|
set level(LogLevel value);
|
||||||
|
void log(String message);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LogLevel { debug, info, warning, error }
|
||||||
|
|
||||||
|
class ConsoleLogger implements Logger {
|
||||||
|
LogLevel _level = LogLevel.info;
|
||||||
|
|
||||||
|
@override
|
||||||
|
LogLevel get level => _level;
|
||||||
|
|
||||||
|
@override
|
||||||
|
set level(LogLevel value) => _level = value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void log(String message) => print('Console: $message');
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late Container container;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
container = Container(MockReflector());
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Service Extender Tests', () {
|
||||||
|
test('can extend a service after resolution', () {
|
||||||
|
container.registerSingleton<Logger>(ConsoleLogger());
|
||||||
|
container.extend<Logger>((logger, container) {
|
||||||
|
logger.level = LogLevel.debug;
|
||||||
|
return logger;
|
||||||
|
});
|
||||||
|
|
||||||
|
var logger = container.make<Logger>();
|
||||||
|
expect(logger.level, equals(LogLevel.debug));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can apply multiple extenders in order', () {
|
||||||
|
container.registerSingleton<Logger>(ConsoleLogger());
|
||||||
|
|
||||||
|
container.extend<Logger>((logger, container) {
|
||||||
|
logger.level = LogLevel.debug;
|
||||||
|
return logger;
|
||||||
|
});
|
||||||
|
|
||||||
|
container.extend<Logger>((logger, container) {
|
||||||
|
logger.level = LogLevel.error;
|
||||||
|
return logger;
|
||||||
|
});
|
||||||
|
|
||||||
|
var logger = container.make<Logger>();
|
||||||
|
expect(logger.level, equals(LogLevel.error));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('child container inherits parent extenders', () {
|
||||||
|
container.registerSingleton<Logger>(ConsoleLogger());
|
||||||
|
container.extend<Logger>((logger, container) {
|
||||||
|
logger.level = LogLevel.debug;
|
||||||
|
return logger;
|
||||||
|
});
|
||||||
|
|
||||||
|
var child = container.createChild();
|
||||||
|
var logger = child.make<Logger>();
|
||||||
|
expect(logger.level, equals(LogLevel.debug));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('child container can add its own extenders', () {
|
||||||
|
container.registerSingleton<Logger>(ConsoleLogger());
|
||||||
|
container.extend<Logger>((logger, container) {
|
||||||
|
logger.level = LogLevel.debug;
|
||||||
|
return logger;
|
||||||
|
});
|
||||||
|
|
||||||
|
var child = container.createChild();
|
||||||
|
child.extend<Logger>((logger, container) {
|
||||||
|
logger.level = LogLevel.error;
|
||||||
|
return logger;
|
||||||
|
});
|
||||||
|
|
||||||
|
var parentLogger = container.make<Logger>();
|
||||||
|
expect(parentLogger.level, equals(LogLevel.debug));
|
||||||
|
|
||||||
|
var childLogger = child.make<Logger>();
|
||||||
|
expect(childLogger.level, equals(LogLevel.error));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue