diff --git a/source/poodinis/autowire.d b/source/poodinis/autowire.d index 0b024e5..3b3651e 100644 --- a/source/poodinis/autowire.d +++ b/source/poodinis/autowire.d @@ -79,7 +79,7 @@ private void printDebugAutowiredInstance(TypeInfo instanceType, void* instanceAd * * See_Also: Autowire */ -public void autowire(Type)(DependencyContainer container, Type instance) { +public void autowire(Type)(shared(DependencyContainer) container, Type instance) { debug(poodinisVerbose) { printDebugAutowiredInstance(typeid(Type), &instance); } @@ -93,7 +93,7 @@ private void printDebugAutowiringCandidate(TypeInfo candidateInstanceType, void* writeln(format("DEBUG: Autowired instance [%s@%s] to [%s@%s].%s", candidateInstanceType, candidateInstanceAddress, instanceType, instanceAddress, member)); } -private void autowireMember(string member, Type)(DependencyContainer container, Type instance) { +private void autowireMember(string member, Type)(shared(DependencyContainer) container, Type instance) { static if(__traits(compiles, __traits(getMember, instance, member)) && __traits(compiles, __traits(getAttributes, __traits(getMember, instance, member)))) { foreach(autowireAttribute; __traits(getAttributes, __traits(getMember, instance, member))) { static if (__traits(isSame, autowireAttribute, Autowire) || is(autowireAttribute == Autowire!T, T)) { @@ -138,9 +138,9 @@ public void globalAutowire(Type)(Type instance) { } class AutowiredRegistration(RegistrationType : Object) : Registration { - private DependencyContainer container; + private shared(DependencyContainer) container; - public this(TypeInfo registeredType, DependencyContainer container) { + public this(TypeInfo registeredType, shared(DependencyContainer) container) { enforce(!(container is null), "Argument 'container' is null. Autowired registrations need to autowire using a container."); this.container = container; super(registeredType, typeid(RegistrationType)); diff --git a/source/poodinis/container.d b/source/poodinis/container.d index d6d49da..32e6fb6 100644 --- a/source/poodinis/container.d +++ b/source/poodinis/container.d @@ -44,7 +44,7 @@ class ResolveException : Exception { * In most cases you want to use a global singleton dependency container provided by getInstance() to manage all dependencies. * You can still create new instances of this class for exceptional situations. */ -class DependencyContainer { +synchronized class DependencyContainer { private Registration[][TypeInfo] registrations; private Registration[] autowireStack; @@ -111,16 +111,16 @@ class DependencyContainer { return existingRegistration; } - AutowiredRegistration!ConcreteType newRegistration = new AutowiredRegistration!ConcreteType(registeredType, this); + auto newRegistration = new AutowiredRegistration!ConcreteType(registeredType, this); newRegistration.singleInstance(); - registrations[registeredType] ~= newRegistration; + registrations[registeredType] ~= cast(shared(Registration)) newRegistration; return newRegistration; } private Registration getExistingRegistration(TypeInfo registrationType, TypeInfo qualifierType) { auto existingCandidates = registrationType in registrations; if (existingCandidates) { - return getRegistration(*existingCandidates, qualifierType); + return getRegistration(cast(Registration[]) *existingCandidates, qualifierType); } return null; @@ -218,13 +218,13 @@ class DependencyContainer { throw new ResolveException("Type not registered.", resolveType); } - Registration registration = getQualifiedRegistration(resolveType, qualifierType, *candidates); + Registration registration = getQualifiedRegistration(resolveType, qualifierType, cast(Registration[]) *candidates); QualifierType instance; - if (!autowireStack.canFind(registration)) { - autowireStack ~= registration; + if (!(cast(Registration[]) autowireStack).canFind(registration)) { + autowireStack ~= cast(shared(Registration)) registration; instance = cast(QualifierType) registration.getInstance(new AutowireInstantiationContext()); - autowireStack.popBack(); + autowireStack = autowireStack[0 .. $-1]; } else { auto autowireContext = new AutowireInstantiationContext(); autowireContext.autowireInstance = false; diff --git a/test/poodinis/autowiretest.d b/test/poodinis/autowiretest.d index 042a343..b810600 100644 --- a/test/poodinis/autowiretest.d +++ b/test/poodinis/autowiretest.d @@ -1,164 +1,164 @@ -/** - * Poodinis Dependency Injection Framework - * Copyright 2014-2015 Mike Bierlee - * This software is licensed under the terms of the MIT license. - * The full terms of the license can be found in the LICENSE file. - */ - -import poodinis.autowire; - -import std.exception; - -version(unittest) { - class ComponentA {} - - class ComponentB { - public @Autowire ComponentA componentA; - - public bool componentIsNull() { - return componentA is null; - } - } - - interface InterfaceA {} - - class ComponentC : InterfaceA {} - - class ComponentD { - public @Autowire InterfaceA componentC = null; - - public bool componentIsNull() { - return componentC is null; - } - } - - class DummyAttribute{}; - - class ComponentE { - @DummyAttribute - public ComponentC componentC; - } - - class ComponentDeclarationCocktail { - alias noomer = int; - - @Autowire - public ComponentA componentA; - - public void doesNothing() { - } - - ~this(){ - } - } - - class ComponentX : InterfaceA {} - - class MonkeyShine { - @Autowire!ComponentX - public InterfaceA component; - } - - class BootstrapBootstrap { - @Autowire!ComponentX - public InterfaceA componentX; - - @Autowire!ComponentC - public InterfaceA componentC; - } - - // Test autowiring concrete type to existing instance - unittest { - auto container = new DependencyContainer(); - container.register!ComponentA; - auto componentB = new ComponentB(); - container.autowire!(ComponentB)(componentB); - assert(!componentB.componentIsNull(), "Autowirable dependency failed to autowire"); - } - - // Test autowiring interface type to existing instance - unittest { - auto container = new DependencyContainer(); - container.register!(InterfaceA, ComponentC); - auto componentD = new ComponentD(); - container.autowire!(ComponentD)(componentD); - assert(!componentD.componentIsNull(), "Autowirable dependency failed to autowire"); - } - - // Test autowiring will only happen once - unittest { - auto container = new DependencyContainer(); - container.register!(InterfaceA, ComponentC).newInstance(); - auto componentD = new ComponentD(); - container.autowire!(ComponentD)(componentD); - auto expectedComponent = componentD.componentC; - container.autowire!(ComponentD)(componentD); - auto actualComponent = componentD.componentC; - assert(expectedComponent is actualComponent, "Autowiring the second time wired a different instance"); - } - - // Test autowiring unregistered type - unittest { - auto container = new DependencyContainer(); - auto componentD = new ComponentD(); - assertThrown!(ResolveException)(container.autowire!(ComponentD)(componentD), "Autowiring unregistered type should throw ResolveException"); - } - - // Test autowiring member with non-autowire attribute does not autowire - unittest { - auto container = new DependencyContainer(); - auto componentE = new ComponentE(); - container.autowire!ComponentE(componentE); - assert(componentE.componentC is null, "Autowiring should not occur for members with attributes other than @Autowire"); - } - - // Test autowire class with alias declaration - unittest { - auto container = new DependencyContainer(); - container.register!ComponentA; - auto componentDeclarationCocktail = new ComponentDeclarationCocktail(); - - container.autowire(componentDeclarationCocktail); - - assert(componentDeclarationCocktail.componentA !is null, "Autowiring class with non-assignable declarations failed"); - } - - // Test autowire class with qualifier - unittest { - auto container = new DependencyContainer(); - container.register!(InterfaceA, ComponentC); - container.register!(InterfaceA, ComponentX); - auto componentX = container.resolve!(InterfaceA, ComponentX); - - auto monkeyShine = new MonkeyShine(); - container.autowire(monkeyShine); - - assert(monkeyShine.component is componentX, "Autowiring class with qualifier failed"); - } - - // Test autowire class with multiple qualifiers - unittest { - auto container = new DependencyContainer(); - container.register!(InterfaceA, ComponentC); - container.register!(InterfaceA, ComponentX); - auto componentC = container.resolve!(InterfaceA, ComponentC); - auto componentX = container.resolve!(InterfaceA, ComponentX); - - auto bootstrapBootstrap = new BootstrapBootstrap(); - container.autowire(bootstrapBootstrap); - - assert(bootstrapBootstrap.componentX is componentX, "Autowiring class with multiple qualifiers failed"); - assert(bootstrapBootstrap.componentC is componentC, "Autowiring class with multiple qualifiers failed"); - } - - // Test getting instance from autowired registration will autowire instance - unittest { - auto container = new DependencyContainer(); - container.register!ComponentA; - - auto registration = new AutowiredRegistration!ComponentB(typeid(ComponentB), container).singleInstance(); - auto instance = cast(ComponentB) registration.getInstance(new AutowireInstantiationContext()); - - assert(!instance.componentIsNull()); - } -} +/** + * Poodinis Dependency Injection Framework + * Copyright 2014-2015 Mike Bierlee + * This software is licensed under the terms of the MIT license. + * The full terms of the license can be found in the LICENSE file. + */ + +import poodinis.autowire; + +import std.exception; + +version(unittest) { + class ComponentA {} + + class ComponentB { + public @Autowire ComponentA componentA; + + public bool componentIsNull() { + return componentA is null; + } + } + + interface InterfaceA {} + + class ComponentC : InterfaceA {} + + class ComponentD { + public @Autowire InterfaceA componentC = null; + + public bool componentIsNull() { + return componentC is null; + } + } + + class DummyAttribute{}; + + class ComponentE { + @DummyAttribute + public ComponentC componentC; + } + + class ComponentDeclarationCocktail { + alias noomer = int; + + @Autowire + public ComponentA componentA; + + public void doesNothing() { + } + + ~this(){ + } + } + + class ComponentX : InterfaceA {} + + class MonkeyShine { + @Autowire!ComponentX + public InterfaceA component; + } + + class BootstrapBootstrap { + @Autowire!ComponentX + public InterfaceA componentX; + + @Autowire!ComponentC + public InterfaceA componentC; + } + + // Test autowiring concrete type to existing instance + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!ComponentA; + auto componentB = new ComponentB(); + container.autowire!(ComponentB)(componentB); + assert(!componentB.componentIsNull(), "Autowirable dependency failed to autowire"); + } + + // Test autowiring interface type to existing instance + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(InterfaceA, ComponentC); + auto componentD = new ComponentD(); + container.autowire!(ComponentD)(componentD); + assert(!componentD.componentIsNull(), "Autowirable dependency failed to autowire"); + } + + // Test autowiring will only happen once + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(InterfaceA, ComponentC).newInstance(); + auto componentD = new ComponentD(); + container.autowire!(ComponentD)(componentD); + auto expectedComponent = componentD.componentC; + container.autowire!(ComponentD)(componentD); + auto actualComponent = componentD.componentC; + assert(expectedComponent is actualComponent, "Autowiring the second time wired a different instance"); + } + + // Test autowiring unregistered type + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + auto componentD = new ComponentD(); + assertThrown!(ResolveException)(container.autowire!(ComponentD)(componentD), "Autowiring unregistered type should throw ResolveException"); + } + + // Test autowiring member with non-autowire attribute does not autowire + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + auto componentE = new ComponentE(); + container.autowire!ComponentE(componentE); + assert(componentE.componentC is null, "Autowiring should not occur for members with attributes other than @Autowire"); + } + + // Test autowire class with alias declaration + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!ComponentA; + auto componentDeclarationCocktail = new ComponentDeclarationCocktail(); + + container.autowire(componentDeclarationCocktail); + + assert(componentDeclarationCocktail.componentA !is null, "Autowiring class with non-assignable declarations failed"); + } + + // Test autowire class with qualifier + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(InterfaceA, ComponentC); + container.register!(InterfaceA, ComponentX); + auto componentX = container.resolve!(InterfaceA, ComponentX); + + auto monkeyShine = new MonkeyShine(); + container.autowire(monkeyShine); + + assert(monkeyShine.component is componentX, "Autowiring class with qualifier failed"); + } + + // Test autowire class with multiple qualifiers + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(InterfaceA, ComponentC); + container.register!(InterfaceA, ComponentX); + auto componentC = container.resolve!(InterfaceA, ComponentC); + auto componentX = container.resolve!(InterfaceA, ComponentX); + + auto bootstrapBootstrap = new BootstrapBootstrap(); + container.autowire(bootstrapBootstrap); + + assert(bootstrapBootstrap.componentX is componentX, "Autowiring class with multiple qualifiers failed"); + assert(bootstrapBootstrap.componentC is componentC, "Autowiring class with multiple qualifiers failed"); + } + + // Test getting instance from autowired registration will autowire instance + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!ComponentA; + + auto registration = new AutowiredRegistration!ComponentB(typeid(ComponentB), container).singleInstance(); + auto instance = cast(ComponentB) registration.getInstance(new AutowireInstantiationContext()); + + assert(!instance.componentIsNull()); + } +} diff --git a/test/poodinis/containertest.d b/test/poodinis/containertest.d index 0881782..e75342d 100644 --- a/test/poodinis/containertest.d +++ b/test/poodinis/containertest.d @@ -1,372 +1,372 @@ -/** - * Poodinis Dependency Injection Framework - * Copyright 2014-2015 Mike Bierlee - * This software is licensed under the terms of the MIT license. - * The full terms of the license can be found in the LICENSE file. - */ - -import poodinis.container; - -import std.exception; - -version(unittest) { - interface TestInterface { - } - - class TestClass : TestInterface { - } - - class UnrelatedClass{ - } - - class FailOnCreationClass { - this() { - throw new Exception("This class should not be instantiated"); - } - } - - class AutowiredClass { - } - - class ComponentClass { - @Autowire - public AutowiredClass autowiredClass; - } - - class ComponentCat { - @Autowire - public ComponentMouse mouse; - } - - class ComponentMouse { - @Autowire - public ComponentCat cat; - } - - class Eenie { - @Autowire - public Meenie meenie; - } - - class Meenie { - @Autowire - public Moe moe; - } - - class Moe { - @Autowire - public Eenie eenie; - } - - class Ittie { - @Autowire - public Bittie bittie; - } - - class Bittie { - @Autowire - public Banana banana; - } - - class Banana { - @Autowire - public Bittie bittie; - } - - interface SuperInterface { - } - - class SuperImplementation : SuperInterface { - @Autowire - public Banana banana; - } - - interface Color { - } - - class Blue : Color { - } - - class Red : Color { - } - - class Spiders { - @Autowire - public TestInterface testMember; - } - - class Recursive { - @Autowire - public Recursive recursive; - } - - // Test register concrete type - unittest { - auto container = new DependencyContainer(); - auto registration = container.register!(TestClass)(); - assert(registration.registeredType == typeid(TestClass), "Type of registered type not the same"); - } - - // Test resolve registered type - unittest { - auto container = new DependencyContainer(); - container.register!(TestClass)(); - TestClass actualInstance = container.resolve!(TestClass)(); - assert(actualInstance !is null, "Resolved type is null"); - assert(cast(TestClass) actualInstance, "Resolved class is not the same type as expected"); - } - - // Test register interface - unittest { - auto container = new DependencyContainer(); - container.register!(TestInterface, TestClass)(); - TestInterface actualInstance = container.resolve!(TestInterface)(); - assert(actualInstance !is null, "Resolved type is null"); - assert(cast(TestInterface) actualInstance, "Resolved class is not the same type as expected"); - } - - // Test resolve non-registered type - unittest { - auto container = new DependencyContainer(); - assertThrown!ResolveException(container.resolve!(TestClass)(), "Resolving non-registered type does not fail"); - } - - // Test clear registrations - unittest { - auto container = new DependencyContainer(); - container.register!(TestClass)(); - container.clearAllRegistrations(); - assertThrown!ResolveException(container.resolve!(TestClass)(), "Resolving cleared type does not fail"); - } - - // Test get singleton of container - unittest { - auto instance1 = DependencyContainer.getInstance(); - auto instance2 = DependencyContainer.getInstance(); - assert(instance1 is instance2, "getInstance does not return the same instance"); - } - - // Test resolve single instance for type - unittest { - auto container = new DependencyContainer(); - container.register!(TestClass)().singleInstance(); - auto instance1 = container.resolve!(TestClass); - auto instance2 = container.resolve!(TestClass); - assert(instance1 is instance2, "Resolved instance from single instance scope is not the each time it is resolved"); - } - - // Test resolve new instance for type - unittest { - auto container = new DependencyContainer(); - container.register!(TestClass)().newInstance(); - auto instance1 = container.resolve!(TestClass); - auto instance2 = container.resolve!(TestClass); - assert(instance1 !is instance2, "Resolved instance from new instance scope is the same each time it is resolved"); - } - - // Test resolve existing instance for type - unittest { - auto container = new DependencyContainer(); - auto expectedInstance = new TestClass(); - container.register!(TestClass)().existingInstance(expectedInstance); - auto actualInstance = container.resolve!(TestClass); - assert(expectedInstance is actualInstance, "Resolved instance from existing instance scope is not the same as the registered instance"); - } - - // Test autowire resolved instances - unittest { - auto container = new DependencyContainer(); - container.register!AutowiredClass; - container.register!ComponentClass; - auto componentInstance = container.resolve!ComponentClass; - auto autowiredInstance = container.resolve!AutowiredClass; - assert(componentInstance.autowiredClass is autowiredInstance, "Member is not autowired upon resolving"); - } - - // Test circular autowiring - unittest { - auto container = new DependencyContainer(); - container.register!ComponentMouse; - container.register!ComponentCat; - auto mouse = container.resolve!ComponentMouse; - auto cat = container.resolve!ComponentCat; - assert(mouse.cat is cat && cat.mouse is mouse && mouse !is cat, "Circular dependencies should be autowirable"); - } - - // Test remove registration - unittest { - auto container = new DependencyContainer(); - container.register!TestClass; - container.removeRegistration!TestClass; - assertThrown!ResolveException(container.resolve!TestClass); - } - - // Test autowiring does not autowire member where instance is non-null - unittest { - auto container = new DependencyContainer(); - auto existingA = new AutowiredClass(); - auto existingB = new ComponentClass(); - existingB.autowiredClass = existingA; - - container.register!AutowiredClass; - container.register!(ComponentClass).existingInstance(existingB); - auto resolvedA = container.resolve!AutowiredClass; - auto resolvedB = container.resolve!ComponentClass; - - assert(resolvedB.autowiredClass is existingA && resolvedA !is existingA, "Autowiring shouldn't rewire member when it is already wired to an instance"); - } - - // Test autowiring circular dependency by third-degree - unittest { - auto container = new DependencyContainer(); - container.register!Eenie; - container.register!Meenie; - container.register!Moe; - - auto eenie = container.resolve!Eenie; - - assert(eenie.meenie.moe.eenie is eenie, "Autowiring third-degree circular dependency failed"); - } - - // Test autowiring deep circular dependencies - unittest { - auto container = new DependencyContainer(); - container.register!Ittie; - container.register!Bittie; - container.register!Banana; - - auto ittie = container.resolve!Ittie; - - assert(ittie.bittie is ittie.bittie.banana.bittie, "Autowiring deep dependencies failed."); - } - - // Test autowiring deep circular dependencies with newInstance scope does not autowire new instance second time - unittest { - auto container = new DependencyContainer(); - container.register!(Ittie).newInstance(); - container.register!(Bittie).newInstance(); - container.register!(Banana).newInstance(); - - auto ittie = container.resolve!Ittie; - - assert(ittie.bittie.banana.bittie.banana is null, "Autowiring deep dependencies with newInstance scope autowired a reoccuring type."); - } - - // Test autowiring type registered by interface - unittest { - auto container = new DependencyContainer(); - container.register!Banana; - container.register!Bittie; - container.register!(SuperInterface, SuperImplementation); - - SuperImplementation superInstance = cast(SuperImplementation) container.resolve!SuperInterface; - - assert(!(superInstance.banana is null), "Instance which was resolved by interface type was not autowired."); - } - - // Test reusing a container after clearing all registrations - unittest { - auto container = new DependencyContainer(); - container.register!Banana; - container.clearAllRegistrations(); - try { - container.resolve!Banana; - } catch (ResolveException e) { - container.register!Banana; - return; - } - assert(false); - } - - // Test register multiple concrete classess to same interface type - unittest { - auto container = new DependencyContainer(); - container.register!(Color, Blue); - container.register!(Color, Red); - } - - // Test removing all registrations for type with multiple registrations. - unittest { - auto container = new DependencyContainer(); - container.register!(Color, Blue); - container.register!(Color, Red); - container.removeRegistration!Color; - } - - // Test registering same registration again - unittest { - auto container = new DependencyContainer(); - auto firstRegistration = container.register!(Color, Blue); - auto secondRegistration = container.register!(Color, Blue); - - assert(firstRegistration is secondRegistration, "First registration is not the same as the second of equal types"); - } - - // Test resolve registration with multiple qualifiers - unittest { - auto container = new DependencyContainer(); - container.register!(Color, Blue); - container.register!(Color, Red); - try { - container.resolve!Color; - } catch (ResolveException e) { - return; - } - assert(false); - } - - // Test resolve registration with multiple qualifiers using a qualifier - unittest { - auto container = new DependencyContainer(); - container.register!(Color, Blue); - container.register!(Color, Red); - auto blueInstance = container.resolve!(Color, Blue); - auto redInstance = container.resolve!(Color, Red); - - assert(blueInstance !is redInstance, "Resolving type with multiple, different registrations yielded the same instance"); - assert(blueInstance !is null, "Resolved blue instance to null"); - assert(redInstance !is null, "Resolved red instance to null"); - } - - // Test autowire of unqualified member typed by interface. - unittest { - auto container = new DependencyContainer(); - container.register!Spiders; - container.register!(TestInterface, TestClass); - - auto instance = container.resolve!Spiders; - - assert(!(instance is null), "Container failed to autowire member by interface"); - } - - // Register existing registration - unittest { - auto container = new DependencyContainer(); - - auto firstRegistration = container.register!TestClass; - auto secondRegistration = container.register!TestClass; - - assert(firstRegistration is secondRegistration, "Registering the same registration twice registers the dependencies twice."); - } - - // Register existing registration by supertype - unittest { - auto container = new DependencyContainer(); - - auto firstRegistration = container.register!(TestInterface, TestClass); - auto secondRegistration = container.register!(TestInterface, TestClass); - - assert(firstRegistration is secondRegistration, "Registering the same registration by super type twice registers the dependencies twice."); - } - - // Resolve dependency depending on itself - unittest { - auto container = new DependencyContainer(); - container.register!Recursive; - - auto instance = container.resolve!Recursive; - - assert(instance.recursive is instance, "Resolving dependency that depends on itself fails."); - assert(instance.recursive.recursive is instance, "Resolving dependency that depends on itself fails."); - } -} +/** + * Poodinis Dependency Injection Framework + * Copyright 2014-2015 Mike Bierlee + * This software is licensed under the terms of the MIT license. + * The full terms of the license can be found in the LICENSE file. + */ + +import poodinis.container; + +import std.exception; + +version(unittest) { + interface TestInterface { + } + + class TestClass : TestInterface { + } + + class UnrelatedClass{ + } + + class FailOnCreationClass { + this() { + throw new Exception("This class should not be instantiated"); + } + } + + class AutowiredClass { + } + + class ComponentClass { + @Autowire + public AutowiredClass autowiredClass; + } + + class ComponentCat { + @Autowire + public ComponentMouse mouse; + } + + class ComponentMouse { + @Autowire + public ComponentCat cat; + } + + class Eenie { + @Autowire + public Meenie meenie; + } + + class Meenie { + @Autowire + public Moe moe; + } + + class Moe { + @Autowire + public Eenie eenie; + } + + class Ittie { + @Autowire + public Bittie bittie; + } + + class Bittie { + @Autowire + public Banana banana; + } + + class Banana { + @Autowire + public Bittie bittie; + } + + interface SuperInterface { + } + + class SuperImplementation : SuperInterface { + @Autowire + public Banana banana; + } + + interface Color { + } + + class Blue : Color { + } + + class Red : Color { + } + + class Spiders { + @Autowire + public TestInterface testMember; + } + + class Recursive { + @Autowire + public Recursive recursive; + } + + // Test register concrete type + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + auto registration = container.register!(TestClass)(); + assert(registration.registeredType == typeid(TestClass), "Type of registered type not the same"); + } + + // Test resolve registered type + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(TestClass)(); + TestClass actualInstance = container.resolve!(TestClass)(); + assert(actualInstance !is null, "Resolved type is null"); + assert(cast(TestClass) actualInstance, "Resolved class is not the same type as expected"); + } + + // Test register interface + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(TestInterface, TestClass)(); + TestInterface actualInstance = container.resolve!(TestInterface)(); + assert(actualInstance !is null, "Resolved type is null"); + assert(cast(TestInterface) actualInstance, "Resolved class is not the same type as expected"); + } + + // Test resolve non-registered type + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + assertThrown!ResolveException(container.resolve!(TestClass)(), "Resolving non-registered type does not fail"); + } + + // Test clear registrations + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(TestClass)(); + container.clearAllRegistrations(); + assertThrown!ResolveException(container.resolve!(TestClass)(), "Resolving cleared type does not fail"); + } + + // Test get singleton of container + unittest { + auto instance1 = DependencyContainer.getInstance(); + auto instance2 = DependencyContainer.getInstance(); + assert(instance1 is instance2, "getInstance does not return the same instance"); + } + + // Test resolve single instance for type + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(TestClass)().singleInstance(); + auto instance1 = container.resolve!(TestClass); + auto instance2 = container.resolve!(TestClass); + assert(instance1 is instance2, "Resolved instance from single instance scope is not the each time it is resolved"); + } + + // Test resolve new instance for type + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(TestClass)().newInstance(); + auto instance1 = container.resolve!(TestClass); + auto instance2 = container.resolve!(TestClass); + assert(instance1 !is instance2, "Resolved instance from new instance scope is the same each time it is resolved"); + } + + // Test resolve existing instance for type + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + auto expectedInstance = new TestClass(); + container.register!(TestClass)().existingInstance(expectedInstance); + auto actualInstance = container.resolve!(TestClass); + assert(expectedInstance is actualInstance, "Resolved instance from existing instance scope is not the same as the registered instance"); + } + + // Test autowire resolved instances + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!AutowiredClass; + container.register!ComponentClass; + auto componentInstance = container.resolve!ComponentClass; + auto autowiredInstance = container.resolve!AutowiredClass; + assert(componentInstance.autowiredClass is autowiredInstance, "Member is not autowired upon resolving"); + } + + // Test circular autowiring + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!ComponentMouse; + container.register!ComponentCat; + auto mouse = container.resolve!ComponentMouse; + auto cat = container.resolve!ComponentCat; + assert(mouse.cat is cat && cat.mouse is mouse && mouse !is cat, "Circular dependencies should be autowirable"); + } + + // Test remove registration + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!TestClass; + container.removeRegistration!TestClass; + assertThrown!ResolveException(container.resolve!TestClass); + } + + // Test autowiring does not autowire member where instance is non-null + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + auto existingA = new AutowiredClass(); + auto existingB = new ComponentClass(); + existingB.autowiredClass = existingA; + + container.register!AutowiredClass; + container.register!(ComponentClass).existingInstance(existingB); + auto resolvedA = container.resolve!AutowiredClass; + auto resolvedB = container.resolve!ComponentClass; + + assert(resolvedB.autowiredClass is existingA && resolvedA !is existingA, "Autowiring shouldn't rewire member when it is already wired to an instance"); + } + + // Test autowiring circular dependency by third-degree + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!Eenie; + container.register!Meenie; + container.register!Moe; + + auto eenie = container.resolve!Eenie; + + assert(eenie.meenie.moe.eenie is eenie, "Autowiring third-degree circular dependency failed"); + } + + // Test autowiring deep circular dependencies + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!Ittie; + container.register!Bittie; + container.register!Banana; + + auto ittie = container.resolve!Ittie; + + assert(ittie.bittie is ittie.bittie.banana.bittie, "Autowiring deep dependencies failed."); + } + + // Test autowiring deep circular dependencies with newInstance scope does not autowire new instance second time + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(Ittie).newInstance(); + container.register!(Bittie).newInstance(); + container.register!(Banana).newInstance(); + + auto ittie = container.resolve!Ittie; + + assert(ittie.bittie.banana.bittie.banana is null, "Autowiring deep dependencies with newInstance scope autowired a reoccuring type."); + } + + // Test autowiring type registered by interface + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!Banana; + container.register!Bittie; + container.register!(SuperInterface, SuperImplementation); + + SuperImplementation superInstance = cast(SuperImplementation) container.resolve!SuperInterface; + + assert(!(superInstance.banana is null), "Instance which was resolved by interface type was not autowired."); + } + + // Test reusing a container after clearing all registrations + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!Banana; + container.clearAllRegistrations(); + try { + container.resolve!Banana; + } catch (ResolveException e) { + container.register!Banana; + return; + } + assert(false); + } + + // Test register multiple concrete classess to same interface type + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(Color, Blue); + container.register!(Color, Red); + } + + // Test removing all registrations for type with multiple registrations. + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(Color, Blue); + container.register!(Color, Red); + container.removeRegistration!Color; + } + + // Test registering same registration again + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + auto firstRegistration = container.register!(Color, Blue); + auto secondRegistration = container.register!(Color, Blue); + + assert(firstRegistration is secondRegistration, "First registration is not the same as the second of equal types"); + } + + // Test resolve registration with multiple qualifiers + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(Color, Blue); + container.register!(Color, Red); + try { + container.resolve!Color; + } catch (ResolveException e) { + return; + } + assert(false); + } + + // Test resolve registration with multiple qualifiers using a qualifier + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(Color, Blue); + container.register!(Color, Red); + auto blueInstance = container.resolve!(Color, Blue); + auto redInstance = container.resolve!(Color, Red); + + assert(blueInstance !is redInstance, "Resolving type with multiple, different registrations yielded the same instance"); + assert(blueInstance !is null, "Resolved blue instance to null"); + assert(redInstance !is null, "Resolved red instance to null"); + } + + // Test autowire of unqualified member typed by interface. + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!Spiders; + container.register!(TestInterface, TestClass); + + auto instance = container.resolve!Spiders; + + assert(!(instance is null), "Container failed to autowire member by interface"); + } + + // Register existing registration + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + + auto firstRegistration = container.register!TestClass; + auto secondRegistration = container.register!TestClass; + + assert(firstRegistration is secondRegistration, "Registering the same registration twice registers the dependencies twice."); + } + + // Register existing registration by supertype + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + + auto firstRegistration = container.register!(TestInterface, TestClass); + auto secondRegistration = container.register!(TestInterface, TestClass); + + assert(firstRegistration is secondRegistration, "Registering the same registration by super type twice registers the dependencies twice."); + } + + // Resolve dependency depending on itself + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!Recursive; + + auto instance = container.resolve!Recursive; + + assert(instance.recursive is instance, "Resolving dependency that depends on itself fails."); + assert(instance.recursive.recursive is instance, "Resolving dependency that depends on itself fails."); + } +}