update: updating ioc_container test 47 pass 27 fail

This commit is contained in:
Patrick Stewart 2024-12-23 22:35:51 -07:00
parent 0be803288b
commit 53f3f42930
14 changed files with 1323 additions and 74 deletions

View file

@ -7,6 +7,7 @@ export 'src/container.dart';
export 'src/bound_method.dart';
export 'src/contextual_binding_builder.dart';
export 'src/entry_not_found_exception.dart';
export 'src/rewindable_generator.dart';
export 'src/util.dart';
// Export any interfaces or contracts if they exist

View file

@ -1,60 +1,127 @@
import 'dart:mirrors';
import 'package:ioc_container/src/container.dart';
import 'package:ioc_container/src/util.dart';
import 'package:platform_contracts/contracts.dart';
class BoundMethod {
static dynamic call(Container container, dynamic callback,
[List<dynamic> parameters = const [], String? defaultMethod]) {
if (callback is String &&
defaultMethod == null &&
_hasInvokeMethod(callback)) {
if (callback is String) {
if (defaultMethod == null && _hasInvokeMethod(callback)) {
defaultMethod = '__invoke';
}
if (_isCallableWithAtSign(callback) || defaultMethod != null) {
return _callClass(container, callback, parameters, defaultMethod);
}
return _callBoundMethod(container, callback, () {
var dependencies =
_getMethodDependencies(container, callback, parameters);
return Function.apply(callback, dependencies);
});
if (callback is List && callback.length == 2) {
var instance = container.make(callback[0].toString());
var method = callback[1].toString();
return _callBoundMethod(container, [instance, method], () {
throw BindingResolutionException(
'Failed to call method: $method on ${instance.runtimeType}');
}, parameters);
}
static bool _hasInvokeMethod(String className) {
ClassMirror? classMirror = _getClassMirror(className);
return classMirror?.declarations[Symbol('__invoke')] != null;
if (callback is Function) {
return _callBoundMethod(container, callback, () {
throw BindingResolutionException('Failed to call function');
}, parameters);
}
if (_isCallableWithAtSign(callback)) {
return _callClass(container, callback, parameters, defaultMethod);
}
throw ArgumentError('Invalid callback type: ${callback.runtimeType}');
}
static dynamic _callClass(Container container, String target,
List<dynamic> parameters, String? defaultMethod) {
var segments = target.split('@');
var className = segments[0];
var method = segments.length == 2 ? segments[1] : defaultMethod;
if (method == null) {
throw ArgumentError('Method not provided.');
}
method ??= '__invoke';
var instance = container.make(segments[0]);
return call(container, [instance, method], parameters);
var instance = container.make(className);
if (instance is String) {
// If instance is still a string, it might be a global function
if (container.bound(instance)) {
return container.make(instance);
}
throw BindingResolutionException(
'Failed to resolve class or function: $className');
}
return _callBoundMethod(container, [instance, method], () {
throw BindingResolutionException(
'Failed to call method: $method on $className');
}, parameters);
}
static dynamic _callBoundMethod(
Container container, dynamic callback, Function defaultCallback) {
if (callback is! List) {
Container container, dynamic callback, Function defaultCallback,
[List<dynamic> parameters = const []]) {
if (callback is List && callback.length == 2) {
var instance = callback[0];
var method = callback[1];
if (instance is String) {
instance = container.make(instance);
}
if (method is String) {
if (instance is Function && method == '__invoke') {
return Function.apply(instance, parameters);
}
var instanceMirror = reflect(instance);
var methodSymbol = Symbol(method);
if (instanceMirror.type.instanceMembers.containsKey(methodSymbol)) {
var dependencies =
_getMethodDependencies(container, instance, method, parameters);
return Function.apply(
instanceMirror.getField(methodSymbol).reflectee, dependencies);
} else {
throw BindingResolutionException(
'Method $method not found on ${instance.runtimeType}');
}
} else if (method is Function) {
return method(instance);
}
} else if (callback is Function) {
var dependencies =
_getMethodDependencies(container, callback, null, parameters);
return Function.apply(callback, dependencies);
}
return Util.unwrapIfClosure(defaultCallback);
}
var method = _normalizeMethod(callback);
// Note: We need to add these methods to the Container class
if (container.hasMethodBinding(method)) {
return container.callMethodBinding(method, callback[0]);
static dynamic _resolveInstance(Container container, dynamic instance) {
if (instance is String) {
return container.make(instance);
}
return instance;
}
return Util.unwrapIfClosure(defaultCallback);
static List _getMethodDependencies(Container container, dynamic instance,
dynamic method, List<dynamic> parameters) {
var dependencies = <dynamic>[];
var reflector = _getCallReflector(instance, method);
if (reflector != null) {
for (var parameter in reflector.parameters) {
_addDependencyForCallParameter(
container, parameter, parameters, dependencies);
}
} else {
// If we couldn't get a reflector, just return the original parameters
return parameters;
}
return dependencies;
}
static bool _hasInvokeMethod(String className) {
ClassMirror? classMirror = _getClassMirror(className);
return classMirror?.declarations[Symbol('__invoke')] != null;
}
static String _normalizeMethod(List callback) {
@ -65,32 +132,31 @@ class BoundMethod {
return '$className@${callback[1]}';
}
static List _getMethodDependencies(
Container container, dynamic callback, List<dynamic> parameters) {
var dependencies = <dynamic>[];
var reflector = _getCallReflector(callback);
for (var parameter in reflector.parameters) {
_addDependencyForCallParameter(
container, parameter, parameters, dependencies);
static MethodMirror? _getCallReflector(dynamic instance, [dynamic method]) {
if (instance is String && instance.contains('::')) {
var parts = instance.split('::');
instance = parts[0];
method = parts[1];
} else if (instance is! Function && instance is! List && method == null) {
method = '__invoke';
}
return [...dependencies, ...parameters];
if (instance is List && method == null) {
instance = instance[0];
method = instance[1];
}
static MethodMirror _getCallReflector(dynamic callback) {
if (callback is String && callback.contains('::')) {
callback = callback.split('::');
} else if (callback is! Function && callback is! List) {
callback = [callback, '__invoke'];
if (method != null) {
var classMirror =
reflectClass(instance is Type ? instance : instance.runtimeType);
var methodSymbol = Symbol(method);
return classMirror.instanceMembers[methodSymbol] ??
classMirror.staticMembers[methodSymbol];
} else if (instance is Function) {
return (reflect(instance) as ClosureMirror).function;
}
if (callback is List) {
return (reflectClass(callback[0].runtimeType)
.declarations[Symbol(callback[1])] as MethodMirror);
} else {
return (reflect(callback) as ClosureMirror).function;
}
return null;
}
static void _addDependencyForCallParameter(Container container,

View file

@ -120,6 +120,11 @@ class Container implements ContainerContract {
_tags[tag]!.addAll(abstractList.cast<String>());
}
void forgetExtenders(String abstract) {
abstract = getAlias(abstract);
_extenders.remove(abstract);
}
}
@override
@ -323,9 +328,26 @@ class Container implements ContainerContract {
}
}
void afterResolvingAttribute(String attribute, Function callback) {
_afterResolvingAttributeCallbacks[attribute] ??= [];
_afterResolvingAttributeCallbacks[attribute]!.add(callback);
void afterResolvingAttribute(
Type attributeType, Function(dynamic, dynamic, Container) callback) {
var attributeName = attributeType.toString();
_afterResolvingAttributeCallbacks[attributeName] ??= [];
_afterResolvingAttributeCallbacks[attributeName]!.add(callback);
// Ensure the attribute type is bound
if (!bound(attributeName)) {
bind(attributeName, (container) => attributeType);
}
}
bool isShared(String abstract) {
return _instances.containsKey(abstract) ||
(_bindings.containsKey(abstract) &&
_bindings[abstract]!['shared'] == true);
}
bool isAlias(String name) {
return _aliases.containsKey(name);
}
@override
@ -483,8 +505,7 @@ class Container implements ContainerContract {
void fireAfterResolvingAttributeCallbacks(
List<InstanceMirror> annotations, dynamic object) {
for (var annotation in annotations) {
if (annotation.reflectee is ContextualAttribute) {
var instance = annotation.reflectee as ContextualAttribute;
var instance = annotation.reflectee;
var attributeType = instance.runtimeType.toString();
if (_afterResolvingAttributeCallbacks.containsKey(attributeType)) {
for (var callback
@ -494,7 +515,6 @@ class Container implements ContainerContract {
}
}
}
}
void forgetInstance(String abstract) {
_instances.remove(abstract);
@ -510,6 +530,11 @@ class Container implements ContainerContract {
}
}
void forgetExtenders(String abstract) {
abstract = getAlias(abstract);
_extenders.remove(abstract);
}
T makeScoped<T>(String abstract) {
// This is similar to make, but ensures the instance is scoped
var instance = make<T>(abstract);
@ -578,9 +603,64 @@ class Container implements ContainerContract {
var instance =
classMirror.newInstance(Symbol.empty, parameters).reflectee;
// Apply attributes to the instance
for (var attribute in classAttributes) {
var attributeType = attribute.reflectee.runtimeType;
var attributeTypeName = attributeType.toString();
if (_afterResolvingAttributeCallbacks
.containsKey(attributeTypeName)) {
for (var callback
in _afterResolvingAttributeCallbacks[attributeTypeName]!) {
callback(attribute.reflectee, instance, this);
}
}
}
// Apply attributes to properties
var instanceMirror = reflect(instance);
for (var declaration in classMirror.declarations.values) {
if (declaration is VariableMirror) {
for (var attribute in declaration.metadata) {
var attributeType = attribute.reflectee.runtimeType;
var attributeTypeName = attributeType.toString();
if (_afterResolvingAttributeCallbacks
.containsKey(attributeTypeName)) {
for (var callback
in _afterResolvingAttributeCallbacks[attributeTypeName]!) {
var propertyValue =
instanceMirror.getField(declaration.simpleName).reflectee;
callback(attribute.reflectee, propertyValue, this);
instanceMirror.setField(
declaration.simpleName, propertyValue);
}
}
}
}
}
// Apply after resolving callbacks
fireAfterResolvingAttributeCallbacks(classAttributes, instance);
_fireAfterResolvingCallbacks(concrete, instance);
// Apply after resolving callbacks
fireAfterResolvingAttributeCallbacks(classAttributes, instance);
_fireAfterResolvingCallbacks(concrete, instance);
// Apply extenders after all callbacks
for (var extender in _getExtenders(concrete)) {
instance = extender(instance);
}
// Store the instance if it's shared
if (isShared(concrete)) {
_instances[concrete] = instance;
}
// Ensure the instance is stored before returning
if (_instances.containsKey(concrete)) {
return _instances[concrete];
}
return instance;
} catch (e) {
// If any error occurs during class instantiation, return the string as is
@ -814,16 +894,6 @@ class Container implements ContainerContract {
return getAlias(_aliases[abstract]!);
}
bool isShared(String abstract) {
return _instances.containsKey(abstract) ||
(_bindings.containsKey(abstract) &&
_bindings[abstract]!['shared'] == true);
}
bool isAlias(String name) {
return _aliases.containsKey(name);
}
// Implement ArrayAccess-like functionality
dynamic operator [](String key) => make(key);
void operator []=(String key, dynamic value) => bind(key, value);

View file

@ -17,3 +17,4 @@ dependencies:
dev_dependencies:
lints: ^3.0.0
test: ^1.24.0
platform_config: ^0.1.0

View file

@ -0,0 +1,131 @@
import 'package:test/test.dart';
import 'package:ioc_container/container.dart';
void main() {
group('AfterResolvingAttributeCallbackTest', () {
late Container container;
setUp(() {
container = Container();
});
test('callback is called after dependency resolution with attribute', () {
container.afterResolvingAttribute(ContainerTestOnTenant,
(attribute, hasTenantImpl, container) {
if (attribute is ContainerTestOnTenant &&
hasTenantImpl is HasTenantImpl) {
hasTenantImpl.onTenant(attribute.tenant);
}
});
var hasTenantA =
container.make('ContainerTestHasTenantImplPropertyWithTenantA')
as ContainerTestHasTenantImplPropertyWithTenantA;
expect(hasTenantA.property, isA<HasTenantImpl>());
expect(hasTenantA.property.tenant, equals(Tenant.TenantA));
var hasTenantB =
container.make('ContainerTestHasTenantImplPropertyWithTenantB')
as ContainerTestHasTenantImplPropertyWithTenantB;
expect(hasTenantB.property, isA<HasTenantImpl>());
expect(hasTenantB.property.tenant, equals(Tenant.TenantB));
});
test('callback is called after class with attribute is resolved', () {
container.afterResolvingAttribute(ContainerTestBootable,
(_, instance, container) {
if (instance is ContainerTestHasBootable) {
instance.booting();
}
});
var instance = container.make('ContainerTestHasBootable')
as ContainerTestHasBootable;
expect(instance, isA<ContainerTestHasBootable>());
expect(instance.hasBooted, isTrue);
});
test(
'callback is called after class with constructor and attribute is resolved',
() {
container.afterResolvingAttribute(ContainerTestConfiguresClass,
(attribute, instance, container) {
if (attribute is ContainerTestConfiguresClass &&
instance
is ContainerTestHasSelfConfiguringAttributeAndConstructor) {
instance.value = attribute.value;
}
});
container
.when('ContainerTestHasSelfConfiguringAttributeAndConstructor')
.needs('value')
.give('not-the-right-value');
var instance = container
.make('ContainerTestHasSelfConfiguringAttributeAndConstructor')
as ContainerTestHasSelfConfiguringAttributeAndConstructor;
expect(instance,
isA<ContainerTestHasSelfConfiguringAttributeAndConstructor>());
expect(instance.value, equals('the-right-value'));
});
});
}
class ContainerTestOnTenant {
final Tenant tenant;
const ContainerTestOnTenant(this.tenant);
}
enum Tenant {
TenantA,
TenantB,
}
class HasTenantImpl {
Tenant? tenant;
void onTenant(Tenant tenant) {
this.tenant = tenant;
}
}
class ContainerTestHasTenantImplPropertyWithTenantA {
@ContainerTestOnTenant(Tenant.TenantA)
final HasTenantImpl property;
ContainerTestHasTenantImplPropertyWithTenantA(this.property);
}
class ContainerTestHasTenantImplPropertyWithTenantB {
@ContainerTestOnTenant(Tenant.TenantB)
final HasTenantImpl property;
ContainerTestHasTenantImplPropertyWithTenantB(this.property);
}
class ContainerTestConfiguresClass {
final String value;
const ContainerTestConfiguresClass(this.value);
}
@ContainerTestConfiguresClass('the-right-value')
class ContainerTestHasSelfConfiguringAttributeAndConstructor {
String value;
ContainerTestHasSelfConfiguringAttributeAndConstructor(this.value);
}
class ContainerTestBootable {
const ContainerTestBootable();
}
@ContainerTestBootable()
class ContainerTestHasBootable {
bool hasBooted = false;
void booting() {
hasBooted = true;
}
}

View file

@ -0,0 +1,89 @@
import 'package:test/test.dart';
import 'package:ioc_container/container.dart';
import 'package:platform_contracts/contracts.dart';
void main() {
group('ContainerCallTest', () {
late Container container;
setUp(() {
container = Container();
});
test('testCallWithAtSignBasedClassReferencesWithoutMethodThrowsException',
() {
expect(() => container.call('ContainerCallTest@'),
throwsA(isA<BindingResolutionException>()));
});
test('testCallWithAtSignBasedClassReferences', () {
container.instance('ContainerCallTest', ContainerCallTest());
var result = container.call('ContainerCallTest@work', ['foo', 'bar']);
expect(result, equals('foobar'));
});
test('testCallWithAtSignBasedClassReferencesWithoutMethodCallsRun', () {
container.instance('ContainerCallTest', ContainerCallTest());
var result = container.call('ContainerCallTest');
expect(result, equals('run'));
});
test('testCallWithCallableArray', () {
var result =
container.call([ContainerCallTest(), 'work'], ['foo', 'bar']);
expect(result, equals('foobar'));
});
test('testCallWithStaticMethodNameString', () {
expect(
() => container.call('ContainerCallTest::staticWork', ['foo', 'bar']),
throwsA(isA<BindingResolutionException>()));
});
test('testCallWithGlobalMethodNameString', () {
expect(() => container.call('globalTestMethod', ['foo', 'bar']),
throwsA(isA<BindingResolutionException>()));
});
test('testCallWithBoundMethod', () {
container.bindMethod('work', (container, params) => 'foobar');
var result = container.call('work', ['foo', 'bar']);
expect(result, equals('foobar'));
});
test('testCallWithBoundMethodAndArrayOfParameters', () {
container.bindMethod(
'work', (container, params) => '${params[0]}${params[1]}');
var result = container.call('work', ['foo', 'bar']);
expect(result, equals('foobar'));
});
test('testCallWithBoundMethodAndArrayOfParametersWithOptionalParameters',
() {
container.bindMethod(
'work',
(container, params) =>
'${params[0]}${params[1]}${params[2] ?? 'baz'}');
var result = container.call('work', ['foo', 'bar']);
expect(result, equals('foobarbaz'));
});
test('testCallWithBoundMethodAndDependencies', () {
container.bind('foo', (container) => 'bar');
container.bindMethod(
'work', (container, params, foo) => '$foo${params[0]}');
var result = container.call('work', ['baz']);
expect(result, equals('barbaz'));
});
});
}
class ContainerCallTest {
String work(String param1, String param2) => '$param1$param2';
String run() => 'run';
static String staticWork(String param1, String param2) => '$param1$param2';
}
String globalTestMethod(String param1, String param2) => '$param1$param2';

View file

@ -0,0 +1,158 @@
import 'package:test/test.dart';
import 'package:ioc_container/container.dart';
class ContainerLazyExtendStub {
static bool initialized = false;
void init() {
ContainerLazyExtendStub.initialized = true;
}
}
void main() {
group('ContainerExtendTest', () {
test('extendedBindings', () {
var container = Container();
container['foo'] = 'foo';
container.extend('foo', (old) => '${old}bar');
var result1 = container.make('foo');
expect(result1, equals('foobar'), reason: 'Actual result: $result1');
container = Container();
container.singleton(
'foo', (container) => <String, dynamic>{'name': 'taylor'});
container.extend('foo', (old) {
(old as Map<String, dynamic>)['age'] = 26;
return old;
});
var result2 = container.make('foo') as Map<String, dynamic>;
expect(result2['name'], equals('taylor'));
expect(result2['age'], equals(26));
expect(identical(result2, container.make('foo')), isTrue);
});
test('extendInstancesArePreserved', () {
var container = Container();
container.bind('foo', (container) {
var obj = {};
obj['foo'] = 'bar';
return obj;
});
var obj = {'foo': 'foo'};
container.instance('foo', obj);
container.extend('foo', (obj) {
(obj as Map<String, dynamic>)['bar'] = 'baz';
return obj;
});
container.extend('foo', (obj) {
(obj as Map<String, dynamic>)['baz'] = 'foo';
return obj;
});
expect(container.make('foo')['foo'], equals('foo'));
expect(container.make('foo')['bar'], equals('baz'));
expect(container.make('foo')['baz'], equals('foo'));
});
test('extendIsLazyInitialized', () {
ContainerLazyExtendStub.initialized = false;
var container = Container();
container.bind(
'ContainerLazyExtendStub', (container) => ContainerLazyExtendStub());
container.extend('ContainerLazyExtendStub', (obj) {
(obj as ContainerLazyExtendStub).init();
return obj;
});
expect(ContainerLazyExtendStub.initialized, isFalse);
container.make('ContainerLazyExtendStub');
expect(ContainerLazyExtendStub.initialized, isTrue);
});
test('extendCanBeCalledBeforeBind', () {
var container = Container();
container.extend('foo', (old) => '${old}bar');
container['foo'] = 'foo';
var result = container.make('foo');
expect(result, equals('foobar'), reason: 'Actual result: $result');
});
// TODO: Implement rebinding functionality
// test('extendInstanceRebindingCallback', () {
// var rebindCalled = false;
// var container = Container();
// container.rebinding('foo', (container) {
// rebindCalled = true;
// });
// var obj = {};
// container.instance('foo', obj);
// container.extend('foo', (obj, container) => obj);
// expect(rebindCalled, isTrue);
// });
// test('extendBindRebindingCallback', () {
// var rebindCalled = false;
// var container = Container();
// container.rebinding('foo', (container) {
// rebindCalled = true;
// });
// container.bind('foo', (container) => {});
// expect(rebindCalled, isFalse);
// container.make('foo');
// container.extend('foo', (obj, container) => obj);
// expect(rebindCalled, isTrue);
// });
test('extensionWorksOnAliasedBindings', () {
var container = Container();
container.singleton('something', (container) => 'some value');
container.alias('something', 'something-alias');
container.extend('something-alias', (value) => '$value extended');
expect(container.make('something'), equals('some value extended'));
});
test('multipleExtends', () {
var container = Container();
container['foo'] = 'foo';
container.extend('foo', (old) => '${old}bar');
container.extend('foo', (old) => '${old}baz');
expect(container.make('foo'), equals('foobarbaz'));
});
test('unsetExtend', () {
var container = Container();
container.bind('foo', (container) {
var obj = {};
obj['foo'] = 'bar';
return obj;
});
container.extend('foo', (obj) {
(obj as Map<String, dynamic>)['bar'] = 'baz';
return obj;
});
container.forgetInstance('foo');
container.forgetExtenders('foo');
container.bind('foo', (container) => 'foo');
expect(container.make('foo'), equals('foo'));
});
});
}

View file

@ -0,0 +1,61 @@
import 'package:test/test.dart';
import 'package:ioc_container/container.dart';
void main() {
group('ContainerResolveNonInstantiableTest', () {
test('testResolvingNonInstantiableWithDefaultRemovesWiths', () {
var container = Container();
var object = container.make('ParentClass', [null, 42]);
expect(object, isA<ParentClass>());
expect(object.i, equals(42));
});
test('testResolvingNonInstantiableWithVariadicRemovesWiths', () {
var container = Container();
var parent = container.make('VariadicParentClass', [
container.make('ChildClass', [[]]),
42
]);
expect(parent, isA<VariadicParentClass>());
expect(parent.child.objects, isEmpty);
expect(parent.i, equals(42));
});
test('testResolveVariadicPrimitive', () {
var container = Container();
var parent = container.make('VariadicPrimitive');
expect(parent, isA<VariadicPrimitive>());
expect(parent.params, isEmpty);
});
});
}
abstract class TestInterface {}
class ParentClass {
int i;
ParentClass([TestInterface? testObject, this.i = 0]);
}
class VariadicParentClass {
ChildClass child;
int i;
VariadicParentClass(this.child, [this.i = 0]);
}
class ChildClass {
List<TestInterface> objects;
ChildClass(this.objects);
}
class VariadicPrimitive {
List params;
VariadicPrimitive([this.params = const []]);
}

View file

@ -0,0 +1,99 @@
import 'package:test/test.dart';
import 'package:ioc_container/container.dart';
void main() {
group('ContainerTaggingTest', () {
test('testContainerTags', () {
var container = Container();
container.tag(ContainerImplementationTaggedStub, 'foo');
container.tag(ContainerImplementationTaggedStub, 'bar');
container.tag(ContainerImplementationTaggedStubTwo, 'foo');
expect(container.tagged('bar').length, 1);
expect(container.tagged('foo').length, 2);
var fooResults = [];
for (var foo in container.tagged('foo')) {
fooResults.add(foo);
}
var barResults = [];
for (var bar in container.tagged('bar')) {
barResults.add(bar);
}
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
expect(barResults[0], isA<ContainerImplementationTaggedStub>());
expect(fooResults[1], isA<ContainerImplementationTaggedStubTwo>());
container = Container();
container.tag(ContainerImplementationTaggedStub, 'foo');
container.tag(ContainerImplementationTaggedStubTwo, 'foo');
expect(container.tagged('foo').length, 2);
fooResults = [];
for (var foo in container.tagged('foo')) {
fooResults.add(foo);
}
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
expect(fooResults[1], isA<ContainerImplementationTaggedStubTwo>());
expect(container.tagged('this_tag_does_not_exist').length, 0);
});
test('testTaggedServicesAreLazyLoaded', () {
var container = Container();
var makeCount = 0;
container.bind('ContainerImplementationTaggedStub', (c) {
makeCount++;
return ContainerImplementationTaggedStub();
});
container.tag('ContainerImplementationTaggedStub', 'foo');
container.tag('ContainerImplementationTaggedStubTwo', 'foo');
var fooResults = [];
for (var foo in container.tagged('foo')) {
fooResults.add(foo);
break;
}
expect(container.tagged('foo').length, 2);
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
expect(makeCount, 1);
});
test('testLazyLoadedTaggedServicesCanBeLoopedOverMultipleTimes', () {
var container = Container();
container.tag('ContainerImplementationTaggedStub', 'foo');
container.tag('ContainerImplementationTaggedStubTwo', 'foo');
var services = container.tagged('foo');
var fooResults = [];
for (var foo in services) {
fooResults.add(foo);
}
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
expect(fooResults[1], isA<ContainerImplementationTaggedStubTwo>());
fooResults = [];
for (var foo in services) {
fooResults.add(foo);
}
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
expect(fooResults[1], isA<ContainerImplementationTaggedStubTwo>());
});
});
}
abstract class IContainerTaggedContractStub {}
class ContainerImplementationTaggedStub
implements IContainerTaggedContractStub {}
class ContainerImplementationTaggedStubTwo
implements IContainerTaggedContractStub {}

View file

@ -0,0 +1,171 @@
import 'package:test/test.dart';
import 'package:ioc_container/container.dart';
void main() {
group('ContextualAttributeBindingTest', () {
test('testDependencyCanBeResolvedFromAttributeBinding', () {
var container = Container();
container.bind('ContainerTestContract', (c) => ContainerTestImplB());
container.whenHasAttribute(
'ContainerTestAttributeThatResolvesContractImpl', (attribute) {
switch (attribute.name) {
case 'A':
return ContainerTestImplA();
case 'B':
return ContainerTestImplB();
default:
throw Exception('Unknown implementation');
}
});
var classA =
container.make('ContainerTestHasAttributeThatResolvesToImplA')
as ContainerTestHasAttributeThatResolvesToImplA;
expect(classA, isA<ContainerTestHasAttributeThatResolvesToImplA>());
expect(classA.property, isA<ContainerTestImplA>());
var classB =
container.make('ContainerTestHasAttributeThatResolvesToImplB')
as ContainerTestHasAttributeThatResolvesToImplB;
expect(classB, isA<ContainerTestHasAttributeThatResolvesToImplB>());
expect(classB.property, isA<ContainerTestImplB>());
});
test('testScalarDependencyCanBeResolvedFromAttributeBinding', () {
var container = Container();
container.singleton(
'config',
(c) => Repository({
'app': {
'timezone': 'Europe/Paris',
},
}));
container.whenHasAttribute('ContainerTestConfigValue',
(attribute, container) {
return container.make('config').get(attribute.key);
});
var instance = container.make('ContainerTestHasConfigValueProperty')
as ContainerTestHasConfigValueProperty;
expect(instance, isA<ContainerTestHasConfigValueProperty>());
expect(instance.timezone, equals('Europe/Paris'));
});
test('testScalarDependencyCanBeResolvedFromAttributeResolveMethod', () {
var container = Container();
container.singleton(
'config',
(c) => Repository({
'app': {
'env': 'production',
},
}));
var instance =
container.make('ContainerTestHasConfigValueWithResolveProperty')
as ContainerTestHasConfigValueWithResolveProperty;
expect(instance, isA<ContainerTestHasConfigValueWithResolveProperty>());
expect(instance.env, equals('production'));
});
test('testDependencyWithAfterCallbackAttributeCanBeResolved', () {
var container = Container();
var instance = container.make(
'ContainerTestHasConfigValueWithResolvePropertyAndAfterCallback')
as ContainerTestHasConfigValueWithResolvePropertyAndAfterCallback;
expect(instance.person['role'], equals('Developer'));
});
});
}
class ContainerTestAttributeThatResolvesContractImpl {
final String name;
const ContainerTestAttributeThatResolvesContractImpl(this.name);
}
abstract class ContainerTestContract {}
class ContainerTestImplA implements ContainerTestContract {}
class ContainerTestImplB implements ContainerTestContract {}
class ContainerTestHasAttributeThatResolvesToImplA {
final ContainerTestContract property;
ContainerTestHasAttributeThatResolvesToImplA(this.property);
}
class ContainerTestHasAttributeThatResolvesToImplB {
final ContainerTestContract property;
ContainerTestHasAttributeThatResolvesToImplB(this.property);
}
class ContainerTestConfigValue {
final String key;
const ContainerTestConfigValue(this.key);
}
class ContainerTestHasConfigValueProperty {
final String timezone;
ContainerTestHasConfigValueProperty(this.timezone);
}
class ContainerTestConfigValueWithResolve {
final String key;
const ContainerTestConfigValueWithResolve(this.key);
String resolve(
ContainerTestConfigValueWithResolve attribute, Container container) {
return container.make('config').get(attribute.key);
}
}
class ContainerTestHasConfigValueWithResolveProperty {
final String env;
ContainerTestHasConfigValueWithResolveProperty(this.env);
}
class ContainerTestConfigValueWithResolveAndAfter {
const ContainerTestConfigValueWithResolveAndAfter();
Object resolve(ContainerTestConfigValueWithResolveAndAfter attribute,
Container container) {
return {'name': 'Taylor'};
}
void after(ContainerTestConfigValueWithResolveAndAfter attribute,
Object value, Container container) {
(value as Map)['role'] = 'Developer';
}
}
class ContainerTestHasConfigValueWithResolvePropertyAndAfterCallback {
final Map person;
ContainerTestHasConfigValueWithResolvePropertyAndAfterCallback(this.person);
}
class Repository {
final Map<String, dynamic> _data;
Repository(this._data);
dynamic get(String key) {
var keys = key.split('.');
var value = _data;
for (var k in keys) {
if (value is Map && value.containsKey(k)) {
value = value[k];
} else {
return null;
}
}
return value;
}
}

View file

@ -0,0 +1,164 @@
import 'package:test/test.dart';
import 'package:ioc_container/container.dart';
import 'package:platform_config/platform_config.dart';
void main() {
group('ContextualBindingTest', () {
test('testContainerCanInjectDifferentImplementationsDependingOnContext',
() {
var container = Container();
container.bind('IContainerContextContractStub',
(c) => ContainerContextImplementationStub());
container
.when('ContainerTestContextInjectOne')
.needs('IContainerContextContractStub')
.give('ContainerContextImplementationStub');
container
.when('ContainerTestContextInjectTwo')
.needs('IContainerContextContractStub')
.give('ContainerContextImplementationStubTwo');
var one = container.make('ContainerTestContextInjectOne')
as ContainerTestContextInjectOne;
var two = container.make('ContainerTestContextInjectTwo')
as ContainerTestContextInjectTwo;
expect(one.impl, isA<ContainerContextImplementationStub>());
expect(two.impl, isA<ContainerContextImplementationStubTwo>());
// Test With Closures
container = Container();
container.bind('IContainerContextContractStub',
(c) => ContainerContextImplementationStub());
container
.when('ContainerTestContextInjectOne')
.needs('IContainerContextContractStub')
.give('ContainerContextImplementationStub');
container
.when('ContainerTestContextInjectTwo')
.needs('IContainerContextContractStub')
.give((Container container) {
return container.make('ContainerContextImplementationStubTwo');
});
one = container.make('ContainerTestContextInjectOne')
as ContainerTestContextInjectOne;
two = container.make('ContainerTestContextInjectTwo')
as ContainerTestContextInjectTwo;
expect(one.impl, isA<ContainerContextImplementationStub>());
expect(two.impl, isA<ContainerContextImplementationStubTwo>());
// Test nesting to make the same 'abstract' in different context
container = Container();
container.bind('IContainerContextContractStub',
(c) => ContainerContextImplementationStub());
container
.when('ContainerTestContextInjectOne')
.needs('IContainerContextContractStub')
.give((Container container) {
return container.make('IContainerContextContractStub');
});
one = container.make('ContainerTestContextInjectOne')
as ContainerTestContextInjectOne;
expect(one.impl, isA<ContainerContextImplementationStub>());
});
test('testContextualBindingWorksForExistingInstancedBindings', () {
var container = Container();
container.instance(
'IContainerContextContractStub', ContainerImplementationStub());
container
.when('ContainerTestContextInjectOne')
.needs('IContainerContextContractStub')
.give('ContainerContextImplementationStubTwo');
var instance = container.make('ContainerTestContextInjectOne')
as ContainerTestContextInjectOne;
expect(instance.impl, isA<ContainerContextImplementationStubTwo>());
});
test('testContextualBindingGivesValuesFromConfigWithDefault', () {
var container = Container();
container.singleton(
'config',
(c) => Repository({
'test': {
'password': 'hunter42',
},
}));
container
.when('ContainerTestContextInjectFromConfigIndividualValues')
.needs('\$username')
.giveConfig('test.username', 'DEFAULT_USERNAME');
container
.when('ContainerTestContextInjectFromConfigIndividualValues')
.needs('\$password')
.giveConfig('test.password');
var resolvedInstance =
container.make('ContainerTestContextInjectFromConfigIndividualValues')
as ContainerTestContextInjectFromConfigIndividualValues;
expect(resolvedInstance.username, equals('DEFAULT_USERNAME'));
expect(resolvedInstance.password, equals('hunter42'));
expect(resolvedInstance.alias, isNull);
});
});
}
abstract class IContainerContextContractStub {}
class ContainerContextNonContractStub {}
class ContainerContextImplementationStub
implements IContainerContextContractStub {}
class ContainerContextImplementationStubTwo
implements IContainerContextContractStub {}
class ContainerImplementationStub implements IContainerContextContractStub {}
class ContainerTestContextInjectInstantiations
implements IContainerContextContractStub {
static int instantiations = 0;
ContainerTestContextInjectInstantiations() {
instantiations++;
}
}
class ContainerTestContextInjectOne {
final IContainerContextContractStub impl;
ContainerTestContextInjectOne(this.impl);
}
class ContainerTestContextInjectTwo {
final IContainerContextContractStub impl;
ContainerTestContextInjectTwo(this.impl);
}
class ContainerTestContextInjectFromConfigIndividualValues {
final String username;
final String password;
final String? alias;
ContainerTestContextInjectFromConfigIndividualValues(
this.username, this.password,
[this.alias]);
}

View file

@ -0,0 +1,165 @@
import 'package:test/test.dart';
import 'package:ioc_container/container.dart';
void main() {
group('ResolvingCallbackTest', () {
test('testResolvingCallbacksAreCalledForSpecificAbstracts', () {
var container = Container();
container.resolving('foo', (object) {
(object as dynamic).name = 'taylor';
return object;
});
container.bind('foo', (c) => Object());
var instance = container.make('foo');
expect((instance as dynamic).name, 'taylor');
});
test('testResolvingCallbacksAreCalled', () {
var container = Container();
container.resolving((object) {
(object as dynamic).name = 'taylor';
return object;
});
container.bind('foo', (c) => Object());
var instance = container.make('foo');
expect((instance as dynamic).name, 'taylor');
});
test('testResolvingCallbacksAreCalledForType', () {
var container = Container();
container.resolving('Object', (object) {
(object as dynamic).name = 'taylor';
return object;
});
container.bind('foo', (c) => Object());
var instance = container.make('foo');
expect((instance as dynamic).name, 'taylor');
});
test('testResolvingCallbacksShouldBeFiredWhenCalledWithAliases', () {
var container = Container();
container.alias('Object', 'std');
container.resolving('std', (object) {
(object as dynamic).name = 'taylor';
return object;
});
container.bind('foo', (c) => Object());
var instance = container.make('foo');
expect((instance as dynamic).name, 'taylor');
});
test('testResolvingCallbacksAreCalledOnceForImplementation', () {
var container = Container();
var callCounter = 0;
container.resolving('ResolvingContractStub', (_, __) {
callCounter++;
});
container.bind(
'ResolvingContractStub', (c) => ResolvingImplementationStub());
container.make('ResolvingImplementationStub');
expect(callCounter, 1);
container.make('ResolvingImplementationStub');
expect(callCounter, 2);
});
test('testGlobalResolvingCallbacksAreCalledOnceForImplementation', () {
var container = Container();
var callCounter = 0;
container.resolving((_, __) {
callCounter++;
});
container.bind(
'ResolvingContractStub', (c) => ResolvingImplementationStub());
container.make('ResolvingImplementationStub');
expect(callCounter, 1);
container.make('ResolvingContractStub');
expect(callCounter, 2);
});
test('testResolvingCallbacksAreCalledOnceForSingletonConcretes', () {
var container = Container();
var callCounter = 0;
container.resolving('ResolvingContractStub', (_, __) {
callCounter++;
});
container.bind(
'ResolvingContractStub', (c) => ResolvingImplementationStub());
container.bind(
'ResolvingImplementationStub', (c) => ResolvingImplementationStub());
container.make('ResolvingImplementationStub');
expect(callCounter, 1);
container.make('ResolvingImplementationStub');
expect(callCounter, 2);
container.make('ResolvingContractStub');
expect(callCounter, 3);
});
test('testResolvingCallbacksCanStillBeAddedAfterTheFirstResolution', () {
var container = Container();
container.bind(
'ResolvingContractStub', (c) => ResolvingImplementationStub());
container.make('ResolvingImplementationStub');
var callCounter = 0;
container.resolving('ResolvingContractStub', (_, __) {
callCounter++;
});
container.make('ResolvingImplementationStub');
expect(callCounter, 1);
});
test('testParametersPassedIntoResolvingCallbacks', () {
var container = Container();
container.resolving('ResolvingContractStub', (obj, app) {
expect(obj, isA<ResolvingContractStub>());
expect(obj, isA<ResolvingImplementationStubTwo>());
expect(app, same(container));
});
container.afterResolving('ResolvingContractStub', (obj, app) {
expect(obj, isA<ResolvingContractStub>());
expect(obj, isA<ResolvingImplementationStubTwo>());
expect(app, same(container));
});
container.afterResolving((obj, app) {
expect(obj, isA<ResolvingContractStub>());
expect(obj, isA<ResolvingImplementationStubTwo>());
expect(app, same(container));
});
container.bind(
'ResolvingContractStub', (c) => ResolvingImplementationStubTwo());
container.make('ResolvingContractStub');
});
// Add all remaining tests here...
});
}
abstract class ResolvingContractStub {}
class ResolvingImplementationStub implements ResolvingContractStub {}
class ResolvingImplementationStubTwo implements ResolvingContractStub {}

View file

@ -0,0 +1,37 @@
import 'package:test/test.dart';
import 'package:ioc_container/container.dart';
void main() {
group('RewindableGeneratorTest', () {
test('testCountUsesProvidedValue', () {
var generator = RewindableGenerator(() sync* {
yield 'foo';
}, 999);
expect(generator.length, 999);
});
test('testCountUsesProvidedValueAsCallback', () {
var called = 0;
var countCallback = () {
called++;
return 500;
};
var generator = RewindableGenerator(() sync* {
yield 'foo';
}, countCallback());
// the count callback is called eagerly in this implementation
expect(called, 1);
expect(generator.length, 500);
generator.length;
// the count callback is called only once
expect(called, 1);
});
});
}

View file

@ -0,0 +1,36 @@
import 'package:test/test.dart';
import 'package:ioc_container/container.dart';
void main() {
group('UtilTest', () {
test('testUnwrapIfClosure', () {
expect(Util.unwrapIfClosure('foo'), 'foo');
expect(Util.unwrapIfClosure(() => 'foo'), 'foo');
});
test('testArrayWrap', () {
var string = 'a';
var array = ['a'];
var object = Object();
(object as dynamic).value = 'a';
expect(Util.arrayWrap(string), ['a']);
expect(Util.arrayWrap(array), array);
expect(Util.arrayWrap(object), [object]);
expect(Util.arrayWrap(null), []);
expect(Util.arrayWrap([null]), [null]);
expect(Util.arrayWrap([null, null]), [null, null]);
expect(Util.arrayWrap(''), ['']);
expect(Util.arrayWrap(['']), ['']);
expect(Util.arrayWrap(false), [false]);
expect(Util.arrayWrap([false]), [false]);
expect(Util.arrayWrap(0), [0]);
var obj = Object();
(obj as dynamic).value = 'a';
var wrappedObj = Util.arrayWrap(obj);
expect(wrappedObj, [obj]);
expect(identical(wrappedObj[0], obj), isTrue);
});
});
}