Skip to content

Commit

Permalink
Add initial support for container in dependency module
Browse files Browse the repository at this point in the history
  • Loading branch information
ncipollo committed Oct 14, 2024
1 parent 63ae525 commit 7ac0c8a
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 92 deletions.
3 changes: 3 additions & 0 deletions Sources/WhoopDIKit/Container/Container.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ public final class Container {
private let serviceDict = ServiceDictionary<DependencyDefinition>()
private var localServiceDict: ServiceDictionary<DependencyDefinition>? = 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)
}
Expand Down
8 changes: 6 additions & 2 deletions Sources/WhoopDIKit/Module/DependencyModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
}

Expand Down Expand Up @@ -74,7 +75,10 @@ open class DependencyModule {
/// }
/// ```
public final func get<T>(_ 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.
Expand Down
55 changes: 55 additions & 0 deletions Tests/WhoopDIKitTests/Container/ContainerTests.swift
Original file line number Diff line number Diff line change
@@ -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<Int> = container.inject()
XCTAssertEqual(42, dependency.value)
}

func test_inject_generic_string() {
container.registerModules(modules: [GoodTestModule()])
let dependency: GenericDependency<String> = 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))
}
}
92 changes: 92 additions & 0 deletions Tests/WhoopDIKitTests/Module/TestModules.swift
Original file line number Diff line number Diff line change
@@ -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<T>: 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
}
90 changes: 0 additions & 90 deletions Tests/WhoopDIKitTests/WhoopDITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>: 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
}

0 comments on commit 7ac0c8a

Please sign in to comment.