From 7ac0c8aef5894a544e4e451f9740775f942dcf69 Mon Sep 17 00:00:00 2001 From: Nick Cipollo Date: Mon, 14 Oct 2024 17:49:28 -0400 Subject: [PATCH] Add initial support for container in dependency module --- Sources/WhoopDIKit/Container/Container.swift | 3 + .../WhoopDIKit/Module/DependencyModule.swift | 8 +- .../Container/ContainerTests.swift | 55 +++++++++++ .../WhoopDIKitTests/Module/TestModules.swift | 92 +++++++++++++++++++ Tests/WhoopDIKitTests/WhoopDITests.swift | 90 ------------------ 5 files changed, 156 insertions(+), 92 deletions(-) create mode 100644 Tests/WhoopDIKitTests/Container/ContainerTests.swift create mode 100644 Tests/WhoopDIKitTests/Module/TestModules.swift diff --git a/Sources/WhoopDIKit/Container/Container.swift b/Sources/WhoopDIKit/Container/Container.swift index 1eea632..c10bc2c 100644 --- a/Sources/WhoopDIKit/Container/Container.swift +++ b/Sources/WhoopDIKit/Container/Container.swift @@ -3,10 +3,13 @@ public final class Container { private let serviceDict = ServiceDictionary() private var localServiceDict: ServiceDictionary? = nil + public init() {} + /// Registers a list of modules with the DI system. /// Typically you will create a `DependencyModule` for your feature, then add it to the module list provided to this method. public func registerModules(modules: [DependencyModule]) { modules.forEach { module in + module.container = self module.defineDependencies() module.addToServiceDictionary(serviceDict: serviceDict) } diff --git a/Sources/WhoopDIKit/Module/DependencyModule.swift b/Sources/WhoopDIKit/Module/DependencyModule.swift index fbebbd8..666e715 100644 --- a/Sources/WhoopDIKit/Module/DependencyModule.swift +++ b/Sources/WhoopDIKit/Module/DependencyModule.swift @@ -3,7 +3,8 @@ import Foundation /// Provides dependencies to the object graph. Modules can be registered with WhoopDI via `WhoopDI.registerModules`. open class DependencyModule { private var dependencies: [DependencyDefinition] = [] - + weak var container: Container? = nil + public init() { } @@ -74,7 +75,10 @@ open class DependencyModule { /// } /// ``` public final func get(_ name: String? = nil, params: Any? = nil) throws -> T { - return try WhoopDI.get(name, params) + guard let container = container else { + return try WhoopDI.get(name, params) + } + return try container.get(name, params) } /// Implement this method to define your dependencies. diff --git a/Tests/WhoopDIKitTests/Container/ContainerTests.swift b/Tests/WhoopDIKitTests/Container/ContainerTests.swift new file mode 100644 index 0000000..f2ec039 --- /dev/null +++ b/Tests/WhoopDIKitTests/Container/ContainerTests.swift @@ -0,0 +1,55 @@ +import XCTest +@testable import WhoopDIKit + +class ContainerTests: XCTestCase { + private let container = Container() + + func test_inject() { + container.registerModules(modules: [GoodTestModule()]) + let dependency: Dependency = container.inject("C_Factory", "param") + XCTAssertTrue(dependency is DependencyC) + } + + func test_inject_generic_integer() { + container.registerModules(modules: [GoodTestModule()]) + let dependency: GenericDependency = container.inject() + XCTAssertEqual(42, dependency.value) + } + + func test_inject_generic_string() { + container.registerModules(modules: [GoodTestModule()]) + let dependency: GenericDependency = container.inject() + XCTAssertEqual("string", dependency.value) + } + + func test_inject_localDefinition() { + container.registerModules(modules: [GoodTestModule()]) + let dependency: Dependency = container.inject("C_Factory") { module in + // Typically you'd override or provide a transient dependency. I'm using the top level dependency here + // for the sake of simplicity. + module.factory(name: "C_Factory") { DependencyA() as Dependency } + } + XCTAssertTrue(dependency is DependencyA) + } + + func test_inject_localDefinition_noOverride() { + container.registerModules(modules: [GoodTestModule()]) + let dependency: Dependency = container.inject("C_Factory", params: "params") { _ in } + XCTAssertTrue(dependency is DependencyC) + } + + func test_inject_localDefinition_withParams() { + container.registerModules(modules: [GoodTestModule()]) + let dependency: Dependency = container.inject("C_Factory", params: "params") { module in + module.factoryWithParams(name: "C_Factory") { params in DependencyB(params) as Dependency } + } + XCTAssertTrue(dependency is DependencyB) + } + + func test_injecting() throws { + throw XCTSkip("TODO: implement once WhoopDI uses a DI container") + container.registerModules(modules: [FakeTestModuleForInjecting()]) + let testInjecting: TestInjectingThing = container.inject() + XCTAssertEqual(testInjecting, TestInjectingThing(name: 1)) + } +} diff --git a/Tests/WhoopDIKitTests/Module/TestModules.swift b/Tests/WhoopDIKitTests/Module/TestModules.swift new file mode 100644 index 0000000..28596e5 --- /dev/null +++ b/Tests/WhoopDIKitTests/Module/TestModules.swift @@ -0,0 +1,92 @@ +import Foundation +@testable import WhoopDIKit + +class GoodTestModule: DependencyModule { + override func defineDependencies() { + factory { DependencyA() } + singleton { DependencyD() } + factory(name: "A_Factory") { DependencyA() as Dependency } + singleton(name: "A_Single") { DependencyA() as Dependency } + + factory { GenericDependency("string") } + factory { GenericDependency(42) } + + factoryWithParams(name: "B_Factory") { params in DependencyB(params) as Dependency } + factoryWithParams { params in DependencyB(params) } + singletonWithParams(name: "B_Single") { params in DependencyB(params) as Dependency } + + factoryWithParams { params in + DependencyC(proto: try self.get("A_Factory"), + concrete: try self.get(params: params)) + } + factoryWithParams(name: "C_Factory") { params in + DependencyC(proto: try self.get("A_Factory"), + concrete: try self.get(params: params)) as Dependency + } + } +} + +class BadTestModule: DependencyModule { + override func defineDependencies() { + factoryWithParams { params in + DependencyC(proto: try self.get("A_Factory"), + concrete: try self.get(params: params)) + } + } +} + +class NilFactoryModule: DependencyModule { + override func defineDependencies() { + factory { nil as Dependency? } + } +} + +class NilSingletonModule: DependencyModule { + override func defineDependencies() { + singleton { nil as Dependency? } + } +} + +protocol Dependency { } + +class DependencyA: Dependency { } + +class DependencyB: Dependency { + private let param: String + + internal init(_ param: String) { + self.param = param + } +} + +class DependencyC: Dependency { + private let proto: Dependency + private let concrete: DependencyB + + internal init(proto: Dependency, concrete: DependencyB) { + self.proto = proto + self.concrete = concrete + } +} + +class DependencyD: Dependency { } + +struct GenericDependency: Dependency { + let value: T + + init(_ value: T) { + self.value = value + } +} + +class FakeTestModuleForInjecting: DependencyModule { + override func defineDependencies() { + factory(name: "FakeName", factory: { 1 }) + } +} + +@Injectable +struct TestInjectingThing: Equatable { + @InjectableName(name: "FakeName") + let name: Int +} diff --git a/Tests/WhoopDIKitTests/WhoopDITests.swift b/Tests/WhoopDIKitTests/WhoopDITests.swift index e33bf10..fadc871 100644 --- a/Tests/WhoopDIKitTests/WhoopDITests.swift +++ b/Tests/WhoopDIKitTests/WhoopDITests.swift @@ -116,93 +116,3 @@ class WhoopDITests: XCTestCase { XCTAssertEqual(testInjecting, TestInjectingThing(name: 1)) } } - -class GoodTestModule: DependencyModule { - override func defineDependencies() { - factory { DependencyA() } - singleton { DependencyD() } - factory(name: "A_Factory") { DependencyA() as Dependency } - singleton(name: "A_Single") { DependencyA() as Dependency } - - factory { GenericDependency("string") } - factory { GenericDependency(42) } - - factoryWithParams(name: "B_Factory") { params in DependencyB(params) as Dependency } - factoryWithParams { params in DependencyB(params) } - singletonWithParams(name: "B_Single") { params in DependencyB(params) as Dependency } - - factoryWithParams { params in - DependencyC(proto: try self.get("A_Factory"), - concrete: try self.get(params: params)) - } - factoryWithParams(name: "C_Factory") { params in - DependencyC(proto: try self.get("A_Factory"), - concrete: try self.get(params: params)) as Dependency - } - } -} - -class BadTestModule: DependencyModule { - override func defineDependencies() { - factoryWithParams { params in - DependencyC(proto: try self.get("A_Factory"), - concrete: try self.get(params: params)) - } - } -} - -class NilFactoryModule: DependencyModule { - override func defineDependencies() { - factory { nil as Dependency? } - } -} - -class NilSingletonModule: DependencyModule { - override func defineDependencies() { - singleton { nil as Dependency? } - } -} - -fileprivate protocol Dependency { } - -fileprivate class DependencyA: Dependency { } - -fileprivate class DependencyB: Dependency { - private let param: String - - internal init(_ param: String) { - self.param = param - } -} - -fileprivate class DependencyC: Dependency { - private let proto: Dependency - private let concrete: DependencyB - - internal init(proto: Dependency, concrete: DependencyB) { - self.proto = proto - self.concrete = concrete - } -} - -fileprivate class DependencyD: Dependency { } - -fileprivate struct GenericDependency: Dependency { - let value: T - - init(_ value: T) { - self.value = value - } -} - -fileprivate class FakeTestModuleForInjecting: DependencyModule { - override func defineDependencies() { - factory(name: "FakeName", factory: { 1 }) - } -} - -@Injectable -fileprivate struct TestInjectingThing: Equatable { - @InjectableName(name: "FakeName") - let name: Int -}