refactor: move container test implementations to incubation
This commit is contained in:
parent
53f3f42930
commit
3e9de4cbb2
52 changed files with 255 additions and 43 deletions
|
@ -35,6 +35,72 @@ class BoundMethod {
|
|||
throw ArgumentError('Invalid callback type: ${callback.runtimeType}');
|
||||
}
|
||||
|
||||
static dynamic _callBoundMethod(
|
||||
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);
|
||||
}
|
||||
if (instance is Map && instance.containsKey(method)) {
|
||||
// Handle the case where instance is a Map and method is a key
|
||||
var result = instance[method];
|
||||
return result is Function
|
||||
? Function.apply(result, parameters)
|
||||
: result;
|
||||
}
|
||||
var instanceMirror = reflect(instance);
|
||||
var methodSymbol = Symbol(method);
|
||||
if (instanceMirror.type.instanceMembers.containsKey(methodSymbol)) {
|
||||
var dependencies =
|
||||
_getMethodDependencies(container, instance, method, parameters);
|
||||
var result = Function.apply(
|
||||
instanceMirror.getField(methodSymbol).reflectee, dependencies);
|
||||
return result is Function
|
||||
? Function.apply(result, parameters)
|
||||
: result;
|
||||
} else if (method == '__invoke' && instance is Function) {
|
||||
return Function.apply(instance, parameters);
|
||||
} else if (instance is Type) {
|
||||
// Handle static methods
|
||||
var classMirror = reflectClass(instance);
|
||||
if (classMirror.staticMembers.containsKey(Symbol(method))) {
|
||||
var dependencies =
|
||||
_getMethodDependencies(container, instance, method, parameters);
|
||||
var result = Function.apply(
|
||||
classMirror.getField(Symbol(method)).reflectee, dependencies);
|
||||
return result is Function
|
||||
? Function.apply(result, parameters)
|
||||
: result;
|
||||
}
|
||||
}
|
||||
// Try to find the method in the global scope
|
||||
var globalMethod = _findGlobalMethod(method);
|
||||
if (globalMethod != null) {
|
||||
var result = Function.apply(globalMethod, parameters);
|
||||
return result is Function
|
||||
? Function.apply(result, parameters)
|
||||
: result;
|
||||
}
|
||||
throw BindingResolutionException(
|
||||
'Method $method not found on ${instance.runtimeType}');
|
||||
} else if (method is Function) {
|
||||
var result = Function.apply(method, [instance, ...parameters]);
|
||||
return result is Function ? Function.apply(result, parameters) : result;
|
||||
}
|
||||
} else if (callback is Function) {
|
||||
var result = Function.apply(callback, parameters);
|
||||
return result is Function ? Function.apply(result, parameters) : result;
|
||||
}
|
||||
return Util.unwrapIfClosure(defaultCallback);
|
||||
}
|
||||
|
||||
static dynamic _callClass(Container container, String target,
|
||||
List<dynamic> parameters, String? defaultMethod) {
|
||||
var segments = target.split('@');
|
||||
|
@ -50,8 +116,11 @@ class BoundMethod {
|
|||
if (container.bound(instance)) {
|
||||
return container.make(instance);
|
||||
}
|
||||
throw BindingResolutionException(
|
||||
'Failed to resolve class or function: $className');
|
||||
// If it's not bound, treat it as a string value
|
||||
return instance;
|
||||
}
|
||||
if (instance is Function && method == '__invoke') {
|
||||
return Function.apply(instance, parameters);
|
||||
}
|
||||
return _callBoundMethod(container, [instance, method], () {
|
||||
throw BindingResolutionException(
|
||||
|
@ -59,46 +128,17 @@ class BoundMethod {
|
|||
}, parameters);
|
||||
}
|
||||
|
||||
static dynamic _callBoundMethod(
|
||||
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);
|
||||
static Function? _findGlobalMethod(String methodName) {
|
||||
var currentMirror = currentMirrorSystem();
|
||||
for (var library in currentMirror.libraries.values) {
|
||||
if (library.declarations.containsKey(Symbol(methodName))) {
|
||||
var declaration = library.declarations[Symbol(methodName)]!;
|
||||
if (declaration is MethodMirror && declaration.isTopLevel) {
|
||||
return library.getField(Symbol(methodName)).reflectee as Function;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
static dynamic _resolveInstance(Container container, dynamic instance) {
|
||||
if (instance is String) {
|
||||
return container.make(instance);
|
||||
}
|
||||
return instance;
|
||||
return null;
|
||||
}
|
||||
|
||||
static List _getMethodDependencies(Container container, dynamic instance,
|
||||
|
@ -111,6 +151,11 @@ class BoundMethod {
|
|||
_addDependencyForCallParameter(
|
||||
container, parameter, parameters, dependencies);
|
||||
}
|
||||
} else if (instance is Map &&
|
||||
method is String &&
|
||||
instance.containsKey(method)) {
|
||||
// If instance is a Map and method is a key, return the value
|
||||
return [instance[method]];
|
||||
} else {
|
||||
// If we couldn't get a reflector, just return the original parameters
|
||||
return parameters;
|
||||
|
@ -119,6 +164,13 @@ class BoundMethod {
|
|||
return dependencies;
|
||||
}
|
||||
|
||||
static dynamic _resolveInstance(Container container, dynamic instance) {
|
||||
if (instance is String) {
|
||||
return container.make(instance);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
static bool _hasInvokeMethod(String className) {
|
||||
ClassMirror? classMirror = _getClassMirror(className);
|
||||
return classMirror?.declarations[Symbol('__invoke')] != null;
|
|
@ -642,10 +642,6 @@ class Container implements ContainerContract {
|
|||
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);
|
|
@ -16,5 +16,6 @@ dependencies:
|
|||
|
||||
dev_dependencies:
|
||||
lints: ^3.0.0
|
||||
mockito: ^5.4.0
|
||||
test: ^1.24.0
|
||||
platform_config: ^0.1.0
|
163
incubation/ioc_container/test/bound_method_test.dart
Normal file
163
incubation/ioc_container/test/bound_method_test.dart
Normal file
|
@ -0,0 +1,163 @@
|
|||
import 'package:test/test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:ioc_container/container.dart';
|
||||
import 'package:platform_contracts/contracts.dart';
|
||||
|
||||
class MockContainer extends Mock implements Container {}
|
||||
|
||||
class TestClass {
|
||||
String testMethod(String param) => 'Test: $param';
|
||||
static String staticMethod(String param) => 'Static: $param';
|
||||
String __invoke(String param) => 'Invoke: $param';
|
||||
}
|
||||
|
||||
class DependencyClass {
|
||||
final String value;
|
||||
DependencyClass(this.value);
|
||||
}
|
||||
|
||||
class ClassWithDependency {
|
||||
String methodWithDependency(DependencyClass dep, String param) =>
|
||||
'${dep.value}: $param';
|
||||
}
|
||||
|
||||
class NestedDependency {
|
||||
final DependencyClass dep;
|
||||
NestedDependency(this.dep);
|
||||
String nestedMethod(String param) => '${dep.value} nested: $param';
|
||||
}
|
||||
|
||||
class ClassWithOptionalParam {
|
||||
String methodWithOptional(String required, [String optional = 'default']) =>
|
||||
'$required - $optional';
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('Container.call', () {
|
||||
late MockContainer container;
|
||||
|
||||
setUp(() {
|
||||
container = MockContainer();
|
||||
});
|
||||
|
||||
test('call with Function', () {
|
||||
var result = container.call((String s) => 'Hello $s', ['World']);
|
||||
expect(result, equals('Hello World'));
|
||||
});
|
||||
|
||||
test('call with class@method string', () {
|
||||
when(container.make('TestClass')).thenReturn(TestClass());
|
||||
var result = container.call('TestClass@testMethod', ['World']);
|
||||
expect(result, equals('Test: World'));
|
||||
});
|
||||
|
||||
test('call with List callback', () {
|
||||
when(container.make('TestClass')).thenReturn(TestClass());
|
||||
var result = container.call(['TestClass', 'testMethod'], ['World']);
|
||||
expect(result, equals('Test: World'));
|
||||
});
|
||||
|
||||
test('call with static method', () {
|
||||
when(container.make('TestClass')).thenReturn(TestClass);
|
||||
var result = container.call(['TestClass', 'staticMethod'], ['World']);
|
||||
expect(result, equals('Static: World'));
|
||||
});
|
||||
|
||||
test('call with Map instance and method key', () {
|
||||
var mapInstance = {'testMethod': (String s) => 'Map: $s'};
|
||||
when(container.make('TestMap')).thenReturn(mapInstance);
|
||||
var result = container.call(['TestMap', 'testMethod'], ['World']);
|
||||
expect(result, equals('Map: World'));
|
||||
});
|
||||
|
||||
test('call with global function', () {
|
||||
when(container.make('globalFunction')).thenReturn(globalFunction);
|
||||
var result = container.call('globalFunction', ['World']);
|
||||
expect(result, equals('Global: World'));
|
||||
});
|
||||
|
||||
test('call with default method', () {
|
||||
when(container.make('TestClass')).thenReturn(TestClass());
|
||||
var result = container.call('TestClass', ['World'], '__invoke');
|
||||
expect(result, equals('Invoke: World'));
|
||||
});
|
||||
|
||||
test('call with __invoke method', () {
|
||||
when(container.make('TestClass')).thenReturn(TestClass());
|
||||
var result = container.call('TestClass', ['World']);
|
||||
expect(result, equals('Invoke: World'));
|
||||
});
|
||||
|
||||
test('call with non-existent method throws BindingResolutionException', () {
|
||||
when(container.make('TestClass')).thenReturn(TestClass());
|
||||
expect(() => container.call('TestClass@nonExistentMethod', ['World']),
|
||||
throwsA(isA<BindingResolutionException>()));
|
||||
});
|
||||
|
||||
test('call with invalid callback type throws ArgumentError', () {
|
||||
expect(
|
||||
() => container.call(123, ['World']), throwsA(isA<ArgumentError>()));
|
||||
});
|
||||
|
||||
test('call method with dependencies', () {
|
||||
when(container.make('ClassWithDependency'))
|
||||
.thenReturn(ClassWithDependency());
|
||||
when(container.make('DependencyClass'))
|
||||
.thenReturn(DependencyClass('Dependency'));
|
||||
var result = container
|
||||
.call(['ClassWithDependency', 'methodWithDependency'], ['World']);
|
||||
expect(result, equals('Dependency: World'));
|
||||
});
|
||||
|
||||
test('call method with overridden dependency', () {
|
||||
when(container.make('ClassWithDependency'))
|
||||
.thenReturn(ClassWithDependency());
|
||||
when(container.make('DependencyClass'))
|
||||
.thenReturn(DependencyClass('Dependency'));
|
||||
var result = container.call(
|
||||
['ClassWithDependency', 'methodWithDependency'],
|
||||
[DependencyClass('Override'), 'World']);
|
||||
expect(result, equals('Override: World'));
|
||||
});
|
||||
|
||||
test('call method with nested dependency', () {
|
||||
when(container.make('NestedDependency'))
|
||||
.thenReturn(NestedDependency(DependencyClass('NestedDep')));
|
||||
var result =
|
||||
container.call(['NestedDependency', 'nestedMethod'], ['World']);
|
||||
expect(result, equals('NestedDep nested: World'));
|
||||
});
|
||||
|
||||
test('call method with optional parameter - provided', () {
|
||||
when(container.make('ClassWithOptionalParam'))
|
||||
.thenReturn(ClassWithOptionalParam());
|
||||
var result = container.call(
|
||||
['ClassWithOptionalParam', 'methodWithOptional'],
|
||||
['Required', 'Provided']);
|
||||
expect(result, equals('Required - Provided'));
|
||||
});
|
||||
|
||||
test('call method with optional parameter - default', () {
|
||||
when(container.make('ClassWithOptionalParam'))
|
||||
.thenReturn(ClassWithOptionalParam());
|
||||
var result = container
|
||||
.call(['ClassWithOptionalParam', 'methodWithOptional'], ['Required']);
|
||||
expect(result, equals('Required - default'));
|
||||
});
|
||||
|
||||
test(
|
||||
'call method with missing required dependency throws BindingResolutionException',
|
||||
() {
|
||||
when(container.make('ClassWithDependency'))
|
||||
.thenReturn(ClassWithDependency());
|
||||
when(container.make('DependencyClass'))
|
||||
.thenThrow(BindingResolutionException('DependencyClass not found'));
|
||||
expect(
|
||||
() => container
|
||||
.call(['ClassWithDependency', 'methodWithDependency'], ['World']),
|
||||
throwsA(isA<BindingResolutionException>()));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
String globalFunction(String param) => 'Global: $param';
|
Loading…
Reference in a new issue