refactor: working on constructor injection 174 pass 3 fail

This commit is contained in:
Patrick Stewart 2024-12-27 12:06:30 -07:00
parent 0fbb79e4d7
commit 9c2297cedf
3 changed files with 318 additions and 6 deletions

View file

@ -8,6 +8,7 @@
*/
import 'dart:async';
import 'dart:mirrors' show AbstractClassInstantiationError;
import 'attributes.dart';
import 'exception.dart';
import 'reflector.dart';
@ -344,10 +345,29 @@ class Container {
}
}
instance = reflectedType.newInstance(
try {
dynamic tempInstance;
try {
tempInstance = reflectedType.newInstance(
isDefault(constructor.name) ? '' : constructor.name,
positional,
named, []).reflectee;
} on AbstractClassInstantiationError {
throw BindingResolutionException(
'Cannot instantiate abstract class ${reflectedType.name}');
}
tempInstance = _applyExtenders(t2, tempInstance);
_fireResolvingCallbacks(tempInstance);
_fireAfterResolvingCallbacks(tempInstance);
return tempInstance as T;
} catch (e) {
if (e is AbstractClassInstantiationError) {
throw BindingResolutionException(
'Cannot instantiate abstract class ${reflectedType.name}');
}
rethrow;
}
}
} finally {
_buildStack.add(t2); // Add it back

View file

@ -564,8 +564,13 @@ class _ReflectedClassMirror extends ReflectedClass {
ReflectedInstance newInstance(
String constructorName, List positionalArguments,
[Map<String, dynamic>? namedArguments, List<Type>? typeArguments]) {
try {
return _ReflectedInstanceMirror(
mirror.newInstance(Symbol(constructorName), positionalArguments));
} on dart.AbstractClassInstantiationError {
throw BindingResolutionException(
'Cannot instantiate abstract class ${mirror.simpleName}');
}
}
/// Checks if this [_ReflectedClassMirror] is equal to another object.

View file

@ -0,0 +1,287 @@
import 'package:test/test.dart';
import 'package:platform_container/container.dart';
import 'package:platform_container/mirrors.dart';
// Test interfaces
abstract class Logger {
void log(String message);
}
abstract class Config {
String get value;
}
abstract class Database {
void connect();
}
// Test implementations
class ConsoleLogger implements Logger {
@override
void log(String message) {}
}
class FileConfig implements Config {
@override
String get value => 'test';
}
class SqlDatabase implements Database {
final Logger logger;
final Config config;
SqlDatabase(this.logger, this.config);
@override
void connect() {}
}
// Tracking implementations for dependency order test
class TrackingLogger implements Logger {
final List<String> order;
static int instanceCount = 0;
TrackingLogger(this.order) {
instanceCount++;
order.add('logger');
}
@override
void log(String message) {}
}
class TrackingConfig implements Config {
final List<String> order;
static int instanceCount = 0;
TrackingConfig(this.order) {
instanceCount++;
order.add('config');
}
@override
String get value => 'test';
}
class TrackingDatabase implements Database {
final Logger logger;
final Config config;
final List<String> order;
static int instanceCount = 0;
TrackingDatabase(this.logger, this.config, this.order) {
instanceCount++;
order.add('database');
}
@override
void connect() {}
}
// Custom config for multiple instances test
class CustomConfig implements Config {
final String _value;
CustomConfig(this._value);
@override
String get value => _value;
}
// Test service with multiple dependencies
class UserService {
final Logger logger;
final Database db;
final Config config;
UserService(this.logger, this.db, this.config);
}
// Test service with optional dependencies
class OptionalDepsService {
final Logger logger;
final Config? config;
OptionalDepsService(this.logger, [this.config]);
}
// Test service with named parameters
class NamedParamsService {
final Logger logger;
final Config? config;
NamedParamsService(this.logger, {this.config});
}
// Test service with mixed parameters
class MixedParamsService {
final Logger logger;
final Database db;
final Config? config;
final String? name;
MixedParamsService(this.logger, this.db, {this.config, this.name});
}
// Test service with nested dependencies
class NestedService {
final UserService userService;
final Logger logger;
NestedService(this.userService, this.logger);
}
void main() {
group('Constructor Injection Tests', () {
late Container container;
setUp(() {
container = Container(MirrorsReflector());
container.bind(Logger).to(ConsoleLogger);
container.bind(Config).to(FileConfig);
container.bind(Database).to(SqlDatabase);
// Reset tracking counters
TrackingLogger.instanceCount = 0;
TrackingConfig.instanceCount = 0;
TrackingDatabase.instanceCount = 0;
});
test('injects basic dependencies', () {
var service = container.make<UserService>();
expect(service, isNotNull);
expect(service.logger, isA<ConsoleLogger>());
expect(service.config, isA<FileConfig>());
expect(service.db, isA<SqlDatabase>());
});
test('injects nested dependencies', () {
var service = container.make<NestedService>();
expect(service, isNotNull);
expect(service.logger, isA<ConsoleLogger>());
expect(service.userService, isA<UserService>());
expect(service.userService.logger, isA<ConsoleLogger>());
expect(service.userService.config, isA<FileConfig>());
expect(service.userService.db, isA<SqlDatabase>());
});
test('handles optional dependencies', () {
container = Container(MirrorsReflector());
container.bind(Logger).to(ConsoleLogger);
var service = container.make<OptionalDepsService>();
expect(service, isNotNull);
expect(service.logger, isA<ConsoleLogger>());
expect(service.config, isNull);
// Now bind config and verify it gets injected
container.bind(Config).to(FileConfig);
service = container.make<OptionalDepsService>();
expect(service.config, isA<FileConfig>());
});
test('handles named parameters', () {
container = Container(MirrorsReflector());
container.bind(Logger).to(ConsoleLogger);
var service = container.make<NamedParamsService>();
expect(service, isNotNull);
expect(service.logger, isA<ConsoleLogger>());
expect(service.config, isNull);
// Now bind config and verify it gets injected
container.bind(Config).to(FileConfig);
service = container.make<NamedParamsService>();
expect(service.config, isA<FileConfig>());
});
test('handles mixed parameters', () {
var service = container.makeWith<MixedParamsService>({'name': 'test'});
expect(service, isNotNull);
expect(service.logger, isA<ConsoleLogger>());
expect(service.db, isA<SqlDatabase>());
expect(service.config, isNull);
expect(service.name, equals('test'));
// Now bind config and verify it gets injected
container.bind(Config).to(FileConfig);
service = container.make<MixedParamsService>();
expect(service.config, isA<FileConfig>());
});
test('handles parameter overrides', () {
var customConfig = FileConfig();
var service = container.makeWith<UserService>({'config': customConfig});
expect(service, isNotNull);
expect(service.logger, isA<ConsoleLogger>());
expect(service.config, same(customConfig));
});
test('resolves dependencies in correct order', () {
var order = <String>[];
container = Container(MirrorsReflector());
container.registerSingleton<Logger>(TrackingLogger(order));
container.registerSingleton<Config>(TrackingConfig(order));
container.registerSingleton<Database>(TrackingDatabase(
container.make<Logger>(), container.make<Config>(), order));
// Create service and verify order
container.make<UserService>();
expect(order, equals(['logger', 'config', 'database']));
expect(TrackingLogger.instanceCount, equals(1),
reason: 'Logger should be instantiated once');
expect(TrackingConfig.instanceCount, equals(1),
reason: 'Config should be instantiated once');
expect(TrackingDatabase.instanceCount, equals(1),
reason: 'Database should be instantiated once');
});
test('throws on missing required dependency', () {
container =
Container(MirrorsReflector()); // Fresh container with no bindings
expect(() => container.make<UserService>(),
throwsA(isA<BindingResolutionException>()));
});
test('handles multiple instances with different configurations', () {
var container1 = Container(MirrorsReflector());
var container2 = Container(MirrorsReflector());
container1.bind(Logger).to(ConsoleLogger);
container2.bind(Logger).to(ConsoleLogger);
container1.bind(Database).to(SqlDatabase);
container2.bind(Database).to(SqlDatabase);
container1.registerSingleton<Config>(CustomConfig('config1'));
container2.registerSingleton<Config>(CustomConfig('config2'));
var service1 = container1.make<UserService>();
var service2 = container2.make<UserService>();
expect(service1.config.value, equals('config1'));
expect(service2.config.value, equals('config2'));
});
test('throws when trying to instantiate abstract class', () {
container = Container(MirrorsReflector());
expect(() => container.make<Logger>(),
throwsA(isA<BindingResolutionException>()));
});
test('throws when dependency is abstract', () {
container = Container(MirrorsReflector());
container.bind(Database).to(SqlDatabase);
expect(() => container.make<Database>(),
throwsA(isA<BindingResolutionException>()));
});
});
}