platform/packages/container/test/laravel_container_test.dart
2024-11-28 16:09:46 -07:00

293 lines
9.5 KiB
Dart

import 'package:platform_contracts/contracts.dart';
import 'package:platform_container/platform_container.dart';
import 'package:test/test.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';
import 'laravel_container_test.mocks.dart';
// Test classes
abstract class PaymentGateway {}
class StripeGateway implements PaymentGateway {}
class PayPalGateway implements PaymentGateway {}
class Service {}
class SpecialService extends Service {}
class Client {
final Service service;
Client(this.service);
}
@GenerateMocks([
ReflectorContract,
ReflectedClassContract,
ReflectedInstanceContract,
ReflectedFunctionContract,
ReflectedParameterContract,
ReflectedTypeContract
])
void main() {
late IlluminateContainer container;
late MockReflectorContract reflector;
late MockReflectedClassContract reflectedClass;
late MockReflectedInstanceContract reflectedInstance;
late MockReflectedFunctionContract reflectedFunction;
late MockReflectedParameterContract reflectedParameter;
late MockReflectedTypeContract reflectedType;
setUp(() {
reflector = MockReflectorContract();
reflectedClass = MockReflectedClassContract();
reflectedInstance = MockReflectedInstanceContract();
reflectedFunction = MockReflectedFunctionContract();
reflectedParameter = MockReflectedParameterContract();
reflectedType = MockReflectedTypeContract();
container = IlluminateContainer(reflector);
// Setup default reflection behavior
when(reflector.reflectClass(any)).thenReturn(null);
when(reflector.reflectFunction(any)).thenReturn(null);
});
group('Laravel Container', () {
test('contextual binding with when/needs/give', () {
// When we set up a contextual binding
container.when(Client).needs<Service>().give(SpecialService());
// Then the contextual binding should be used
when(reflector.reflectClass(Client)).thenReturn(reflectedClass);
when(reflectedClass.newInstance('', [])).thenReturn(reflectedInstance);
when(reflectedInstance.reflectee).thenReturn(Client(SpecialService()));
final client = container.make<Client>();
expect(client.service, isA<SpecialService>());
});
test('method injection with call', () {
void method(Service service) {}
// Setup reflection mocks
when(reflector.reflectFunction(method)).thenReturn(reflectedFunction);
when(reflectedFunction.parameters).thenReturn([reflectedParameter]);
when(reflectedParameter.type).thenReturn(reflectedType);
when(reflectedType.reflectedType).thenReturn(Service);
// Register service
final service = Service();
container.registerSingleton(service);
// When we call the method through the container
container.call(method);
// Then dependencies should be injected
verify(reflector.reflectFunction(method)).called(1);
});
test('resolution hooks', () {
// Given some resolution hooks
final resolving = <String>[];
final testValue = 'test';
// Register hooks with explicit type parameters
void beforeCallback(ContainerContract c, String? i) =>
resolving.add('before');
void resolvingCallback(ContainerContract c, String i) =>
resolving.add('resolving');
void afterCallback(ContainerContract c, String i) =>
resolving.add('after');
container.beforeResolving<String>(beforeCallback);
container.resolving<String>(resolvingCallback);
container.afterResolving<String>(afterCallback);
// Register a value to resolve
container.registerSingleton<String>(testValue);
// When we resolve a type
container.make<String>();
// Then hooks should be called in order
expect(resolving, equals(['before', 'resolving', 'after']));
});
test('method binding', () {
// Setup reflection mocks
when(reflector.reflectFunction(any)).thenReturn(reflectedFunction);
when(reflectedFunction.parameters).thenReturn([]);
// Register a method
String boundMethod() => 'bound';
container.bindMethod('test', boundMethod);
// Call a test method
String testMethod() => 'test';
final result = container.call(testMethod);
// Verify the method was called
verify(reflector.reflectFunction(any)).called(1);
expect(result, equals('test'));
});
test('tagged bindings', () {
// Create gateway instances
final stripeGateway = StripeGateway();
final paypalGateway = PayPalGateway();
// Register factories for concrete types
container.registerFactory<StripeGateway>((c) => stripeGateway);
container.registerFactory<PayPalGateway>((c) => paypalGateway);
// Register types in order
container.tag([StripeGateway, PayPalGateway], 'gateways');
// When we resolve tagged bindings
final gateways = container.tagged<PaymentGateway>('gateways');
// Then all tagged instances should be returned in order
expect(gateways.length, equals(2));
expect(gateways[0], same(stripeGateway));
expect(gateways[1], same(paypalGateway));
});
test('shared bindings', () {
// Given a shared binding
container.bind<String>((c) => 'test', shared: true);
// When we resolve multiple times
final first = container.make<String>();
final second = container.make<String>();
// Then the same instance should be returned
expect(identical(first, second), isTrue);
});
test('child container inherits bindings', () {
// Given a parent binding
container.bind<String>((c) => 'parent');
// When we create a child container
final child = container.createChild();
// Then the child should inherit bindings
expect(child.make<String>(), equals('parent'));
});
test('child container can override bindings', () {
// Given a parent binding
container.registerFactory<String>((c) => 'parent');
// When we override in child
final child = container.createChild();
child.registerFactory<String>((c) => 'child');
// Then child should use its own binding
expect(child.make<String>(), equals('child'));
expect(container.make<String>(), equals('parent'));
});
test('scoped bindings', () {
// Given a scoped binding
var counter = 0;
container.scoped<String>((c) => 'scoped${counter++}');
// When we resolve multiple times
final first = container.make<String>();
final second = container.make<String>();
// Then the same instance should be returned
expect(first, equals(second));
// When we forget scoped instances
container.forgetScopedInstances();
// Then a new instance should be created
final third = container.make<String>();
expect(third, isNot(equals(second)));
});
test('type aliases', () {
// Given a binding and an alias
container.bind<PaymentGateway>((c) => StripeGateway());
container.alias(PaymentGateway, StripeGateway);
// When we check if it's an alias
final isAlias = container.isAlias('StripeGateway');
// Then it should be true
expect(isAlias, isTrue);
// And the alias should resolve to the original type
expect(container.getAlias(StripeGateway), equals(PaymentGateway));
});
test('extending instances', () {
print('Starting extending instances test');
// Given a binding and an extender
var extended = false;
print('Setting up binding and extender');
container.bind<Service>((c) => Service());
container.extend(Service, (instance) {
print('Extender called');
extended = true;
});
print('Making instance');
// When we make an instance
container.make<Service>();
print('Extended value: $extended');
// Then the extender should be called
expect(extended, isTrue);
// And we can get the extenders
expect(container.getExtenders(Service).length, equals(1));
// And we can forget them
container.forgetExtenders(Service);
expect(container.getExtenders(Service).length, equals(0));
});
test('rebinding callbacks', () {
// Given a binding and a rebind callback
var rebound = false;
container.bind<Service>((c) => Service());
container.rebinding(Service, (c, i) => rebound = true);
// When we refresh an instance
final target = {'method': (Service s) {}};
container.refresh(Service, target, 'method');
// Then the callback should be called
expect(rebound, isTrue);
});
test('container flushing', () {
// Given some bindings and instances
container.bind<Service>((c) => Service());
container.registerSingleton(PayPalGateway());
container.registerNamedSingleton('test', 'value');
container.tag([Service], 'services');
container.when(Client).needs<Service>().give(SpecialService());
container.bindMethod('test', () {});
container.alias(PaymentGateway, PayPalGateway);
container.extend(Service, (i) {});
container.rebinding(Service, (c, i) {});
container.scoped<String>((c) => 'scoped');
container.beforeResolving<Service>((c, i) {});
// When we flush the container
container.flush();
// Then everything should be cleared
expect(container.has<Service>(), isFalse);
expect(container.hasNamed('test'), isFalse);
expect(() => container.tagged('services'), throwsStateError);
expect(container.getExtenders(Service).length, equals(0));
});
});
}