Skip to content

Commit f531742

Browse files
committed
added tests
1 parent f76d19d commit f531742

12 files changed

+383
-35
lines changed

Package.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version:5.5
1+
// swift-tools-version:5.8
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import PackageDescription
@@ -23,7 +23,8 @@ let package = Package(
2323
dependencies: [],
2424
path: "Sources",
2525
swiftSettings: [
26-
.define("APPLICATION_EXTENSION_API_ONLY")
26+
.define("APPLICATION_EXTENSION_API_ONLY"),
27+
.enableUpcomingFeature("StrictConcurrency")
2728
]
2829
),
2930
.testTarget(

Sources/Container/AsyncContainer.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ public actor AsyncContainer: AsyncDependencyResolving, AsyncDependencyRegisterin
2121
public init() {}
2222

2323
/// Remove all registrations and already instantiated shared instances from the container
24-
func clean() {
24+
public func clean() {
2525
registrations.removeAll()
2626

2727
releaseSharedInstances()
2828
}
2929

3030
/// Remove already instantiated shared instances from the container
31-
func releaseSharedInstances() {
31+
public func releaseSharedInstances() {
3232
sharedInstances.removeAll()
3333
}
3434

@@ -87,7 +87,7 @@ public actor AsyncContainer: AsyncDependencyResolving, AsyncDependencyRegisterin
8787
/// - Parameters:
8888
/// - type: Type of the dependency that should be resolved
8989
/// - argument: Argument that will passed as an input parameter to the factory method that was defined with `register` method
90-
public func tryResolve<Dependency, Argument>(type: Dependency.Type, argument: Argument) async throws -> Dependency {
90+
public func tryResolve<Dependency: Sendable, Argument: Sendable>(type: Dependency.Type, argument: Argument) async throws -> Dependency {
9191
let identifier = RegistrationIdentifier(type: type, argument: Argument.self)
9292

9393
let registration = try getRegistration(with: identifier)
@@ -104,7 +104,7 @@ public actor AsyncContainer: AsyncDependencyResolving, AsyncDependencyRegisterin
104104
///
105105
/// - Parameters:
106106
/// - type: Type of the dependency that should be resolved
107-
public func tryResolve<Dependency>(type: Dependency.Type) async throws -> Dependency {
107+
public func tryResolve<Dependency: Sendable>(type: Dependency.Type) async throws -> Dependency {
108108
let identifier = RegistrationIdentifier(type: type)
109109

110110
let registration = try getRegistration(with: identifier)
@@ -127,7 +127,7 @@ private extension AsyncContainer {
127127
return registration
128128
}
129129

130-
func getDependency<Dependency>(from registration: AsyncRegistration, with argument: Any? = nil) async throws -> Dependency {
130+
func getDependency<Dependency: Sendable>(from registration: AsyncRegistration, with argument: (any Sendable)? = nil) async throws -> Dependency {
131131
switch registration.scope {
132132
case .shared:
133133
if let dependency = sharedInstances[registration.identifier] as? Dependency {

Sources/Container/Container.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import Foundation
99

1010
/// Dependency Injection Container where dependencies are registered and from where they are consequently retrieved (i.e. resolved)
11-
open class Container: DependencyWithArgumentAutoregistering, DependencyAutoregistering, DependencyWithArgumentResolving {
11+
open class Container: DependencyWithArgumentAutoregistering, DependencyAutoregistering, DependencyWithArgumentResolving, @unchecked Sendable {
1212
/// Shared singleton
1313
public static let shared: Container = {
1414
Container()

Sources/Models/AsyncRegistration.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,23 @@
77

88
import Foundation
99

10+
typealias AsyncRegistrationFactory = @Sendable (any AsyncDependencyResolving, (any Sendable)?) async throws -> any Sendable
11+
1012
/// Object that represents a registered dependency and stores a closure, i.e. a factory that returns the desired dependency
11-
struct AsyncRegistration {
13+
struct AsyncRegistration: Sendable {
1214
let identifier: RegistrationIdentifier
1315
let scope: DependencyScope
14-
let factory: (any AsyncDependencyResolving, Any?) async throws -> Any
16+
let factory: AsyncRegistrationFactory
1517

1618
/// Initializer for registrations that don't need any variable argument
17-
init<T>(type: T.Type, scope: DependencyScope, factory: @escaping (any AsyncDependencyResolving) async -> T) {
19+
init<T: Sendable>(type: T.Type, scope: DependencyScope, factory: @Sendable @escaping (any AsyncDependencyResolving) async -> T) {
1820
self.identifier = RegistrationIdentifier(type: type)
1921
self.scope = scope
2022
self.factory = { resolver, _ in await factory(resolver) }
2123
}
2224

2325
/// Initializer for registrations that expect a variable argument passed to the factory closure when the dependency is being resolved
24-
init<T, Argument>(type: T.Type, scope: DependencyScope, factory: @escaping (any AsyncDependencyResolving, Argument) async -> T) {
26+
init<T: Sendable, Argument: Sendable>(type: T.Type, scope: DependencyScope, factory: @Sendable @escaping (any AsyncDependencyResolving, Argument) async -> T) {
2527
let registrationIdentifier = RegistrationIdentifier(type: type, argument: Argument.self)
2628

2729
self.identifier = registrationIdentifier

Sources/Models/DependencyScope.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import Foundation
99

1010
/// Scope of a dependency
11-
public enum DependencyScope {
11+
public enum DependencyScope: Sendable {
1212
/// A new instance of the dependency is created each time the dependency is resolved from the container.
1313
case new
1414

Sources/Protocols/Registration/AsyncDependencyRegistering.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@ import Foundation
1010
/// A type that is able to register a dependency
1111
public protocol AsyncDependencyRegistering {
1212
/// Factory closure that instantiates the required dependency
13-
typealias Factory<Dependency> = (any AsyncDependencyResolving) async -> Dependency
13+
typealias Factory<Dependency: Sendable> = @Sendable (any AsyncDependencyResolving) async -> Dependency
1414

1515
/// Factory closure that instantiates the required dependency with the given variable argument
16-
typealias FactoryWithArgument<Dependency, Argument> = (any AsyncDependencyResolving, Argument) async -> Dependency
16+
typealias FactoryWithArgument<Dependency: Sendable, Argument: Sendable> = @Sendable (any AsyncDependencyResolving, Argument) async -> Dependency
1717

1818
/// Register a dependency
1919
///
2020
/// - Parameters:
2121
/// - type: Type of the dependency to register
2222
/// - scope: Scope of the dependency. If `.new` is used, the `factory` closure is called on each `resolve` call. If `.shared` is used, the `factory` closure is called only the first time, the instance is cached and it is returned for all subsequent `resolve` calls, i.e. it is a singleton
2323
/// - factory: Closure that is called when the dependency is being resolved
24-
func register<Dependency>(type: Dependency.Type, in scope: DependencyScope, factory: @escaping Factory<Dependency>) async
24+
func register<Dependency: Sendable>(type: Dependency.Type, in scope: DependencyScope, factory: @escaping Factory<Dependency>) async
2525

2626
/// Register a dependency with a variable argument
2727
///
@@ -37,7 +37,7 @@ public protocol AsyncDependencyRegistering {
3737
/// - Parameters:
3838
/// - type: Type of the dependency to register
3939
/// - factory: Closure that is called when the dependency is being resolved
40-
func register<Dependency, Argument>(type: Dependency.Type, factory: @escaping FactoryWithArgument<Dependency, Argument>) async
40+
func register<Dependency: Sendable, Argument: Sendable>(type: Dependency.Type, factory: @escaping FactoryWithArgument<Dependency, Argument>) async
4141
}
4242

4343
// MARK: Overloaded factory methods
@@ -54,7 +54,7 @@ public extension AsyncDependencyRegistering {
5454
/// - Parameters:
5555
/// - type: Type of the dependency to register
5656
/// - factory: Closure that is called when the dependency is being resolved
57-
func register<Dependency>(type: Dependency.Type, factory: @escaping Factory<Dependency>) async {
57+
func register<Dependency: Sendable>(type: Dependency.Type, factory: @escaping Factory<Dependency>) async {
5858
await register(type: type, in: Self.defaultScope, factory: factory)
5959
}
6060

@@ -63,15 +63,15 @@ public extension AsyncDependencyRegistering {
6363
/// - Parameters:
6464
/// - scope: Scope of the dependency. If `.new` is used, the `factory` closure is called on each `resolve` call. If `.shared` is used, the `factory` closure is called only the first time, the instance is cached and it is returned for all subsequent `resolve` calls, i.e. it is a singleton
6565
/// - factory: Closure that is called when the dependency is being resolved
66-
func register<Dependency>(in scope: DependencyScope, factory: @escaping Factory<Dependency>) async {
66+
func register<Dependency: Sendable>(in scope: DependencyScope, factory: @escaping Factory<Dependency>) async {
6767
await register(type: Dependency.self, in: scope, factory: factory)
6868
}
6969

7070
/// Register a dependency with an implicit type determined by the factory closure return type and in the default ``DependencyScope``, i.e. in the `shared` scope
7171
///
7272
/// - Parameters:
7373
/// - factory: Closure that is called when the dependency is being resolved
74-
func register<Dependency>(factory: @escaping Factory<Dependency>) async {
74+
func register<Dependency: Sendable>(factory: @escaping Factory<Dependency>) async {
7575
await register(type: Dependency.self, in: Self.defaultScope, factory: factory)
7676
}
7777

@@ -88,7 +88,7 @@ public extension AsyncDependencyRegistering {
8888
///
8989
/// - Parameters:
9090
/// - factory: Closure that is called when the dependency is being resolved
91-
func register<Dependency, Argument>(factory: @escaping FactoryWithArgument<Dependency, Argument>) async {
91+
func register<Dependency: Sendable, Argument: Sendable>(factory: @escaping FactoryWithArgument<Dependency, Argument>) async {
9292
await register(type: Dependency.self, factory: factory)
9393
}
9494
}

Sources/Protocols/Resolution/AsyncDependencyResolving.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public protocol AsyncDependencyResolving {
1515
///
1616
/// - Parameters:
1717
/// - type: Type of the dependency that should be resolved
18-
func tryResolve<T>(type: T.Type) async throws -> T
18+
func tryResolve<T: Sendable>(type: T.Type) async throws -> T
1919

2020
/// Resolve a dependency with a variable argument that was previously registered within the container
2121
///
@@ -24,7 +24,7 @@ public protocol AsyncDependencyResolving {
2424
/// - Parameters:
2525
/// - type: Type of the dependency that should be resolved
2626
/// - argument: Argument that will be passed as an input parameter to the factory method
27-
func tryResolve<T, Argument>(type: T.Type, argument: Argument) async throws -> T
27+
func tryResolve<T: Sendable, Argument: Sendable>(type: T.Type, argument: Argument) async throws -> T
2828
}
2929

3030
public extension AsyncDependencyResolving {
@@ -34,7 +34,7 @@ public extension AsyncDependencyResolving {
3434
///
3535
/// - Parameters:
3636
/// - type: Type of the dependency that should be resolved
37-
func resolve<T>(type: T.Type) async -> T {
37+
func resolve<T: Sendable>(type: T.Type) async -> T {
3838
try! await tryResolve(type: type)
3939
}
4040

@@ -44,7 +44,7 @@ public extension AsyncDependencyResolving {
4444
///
4545
/// - Parameters:
4646
/// - type: Type of the dependency that should be resolved
47-
func resolve<T>() async -> T {
47+
func resolve<T: Sendable>() async -> T {
4848
await resolve(type: T.self)
4949
}
5050

@@ -55,7 +55,7 @@ public extension AsyncDependencyResolving {
5555
/// - Parameters:
5656
/// - type: Type of the dependency that should be resolved
5757
/// - argument: Argument that will be passed as an input parameter to the factory method
58-
func resolve<T, Argument>(type: T.Type, argument: Argument) async -> T {
58+
func resolve<T: Sendable, Argument: Sendable>(type: T.Type, argument: Argument) async -> T {
5959
try! await tryResolve(type: type, argument: argument)
6060
}
6161

@@ -66,7 +66,7 @@ public extension AsyncDependencyResolving {
6666
/// - Parameters:
6767
/// - type: Type of the dependency that should be resolved
6868
/// - argument: Argument that will be passed as an input parameter to the factory method
69-
func resolve<T, Argument>(argument: Argument) async -> T {
69+
func resolve<T: Sendable, Argument: Sendable>(argument: Argument) async -> T {
7070
await resolve(type: T.self, argument: argument)
7171
}
7272
}

Tests/Common/AsyncDITestCase.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// AsyncDITestCase.swift
3+
// DependencyInjection
4+
//
5+
// Created by Róbert Oravec on 19.12.2024.
6+
//
7+
8+
import XCTest
9+
import DependencyInjection
10+
11+
class AsyncDITestCase: XCTestCase {
12+
var container: AsyncContainer!
13+
14+
override func setUp() {
15+
super.setUp()
16+
17+
container = AsyncContainer()
18+
}
19+
20+
override func tearDown() {
21+
container = nil
22+
23+
super.tearDown()
24+
}
25+
}

Tests/Common/Dependencies.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,33 @@
77

88
import Foundation
99

10-
protocol DIProtocol {}
10+
protocol DIProtocol: Sendable {}
1111

1212
struct StructureDependency: Equatable, DIProtocol {
1313
static let `default` = StructureDependency(property1: "test")
1414

1515
let property1: String
1616
}
1717

18-
class SimpleDependency: DIProtocol {}
18+
final class SimpleDependency: DIProtocol {}
1919

20-
class DependencyWithValueTypeParameter {
20+
final class DependencyWithValueTypeParameter: Sendable {
2121
let subDependency: StructureDependency
2222

2323
init(subDependency: StructureDependency = .default) {
2424
self.subDependency = subDependency
2525
}
2626
}
2727

28-
class DependencyWithParameter {
28+
final class DependencyWithParameter: Sendable {
2929
let subDependency: SimpleDependency
3030

3131
init(subDependency: SimpleDependency) {
3232
self.subDependency = subDependency
3333
}
3434
}
3535

36-
class DependencyWithParameter2 {
36+
final class DependencyWithParameter2: Sendable {
3737
let subDependency1: SimpleDependency
3838
let subDependency2: DependencyWithValueTypeParameter
3939

@@ -43,7 +43,7 @@ class DependencyWithParameter2 {
4343
}
4444
}
4545

46-
class DependencyWithParameter3 {
46+
final class DependencyWithParameter3: Sendable {
4747
let subDependency1: SimpleDependency
4848
let subDependency2: DependencyWithValueTypeParameter
4949
let subDependency3: DependencyWithParameter
@@ -59,7 +59,7 @@ class DependencyWithParameter3 {
5959
}
6060
}
6161

62-
class DependencyWithParameter4 {
62+
final class DependencyWithParameter4: Sendable {
6363
let subDependency1: SimpleDependency
6464
let subDependency2: DependencyWithValueTypeParameter
6565
let subDependency3: DependencyWithParameter
@@ -78,7 +78,7 @@ class DependencyWithParameter4 {
7878
}
7979
}
8080

81-
class DependencyWithParameter5 {
81+
final class DependencyWithParameter5: Sendable {
8282
let subDependency1: SimpleDependency
8383
let subDependency2: DependencyWithValueTypeParameter
8484
let subDependency3: DependencyWithParameter
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//
2+
// AsyncArgumentTests.swift
3+
// DependencyInjection
4+
//
5+
// Created by Róbert Oravec on 19.12.2024.
6+
//
7+
8+
import XCTest
9+
import DependencyInjection
10+
11+
final class AsyncContainerArgumentTests: AsyncDITestCase {
12+
func testRegistration() async {
13+
await container.register { (resolver, argument) -> DependencyWithValueTypeParameter in
14+
DependencyWithValueTypeParameter(subDependency: argument)
15+
}
16+
17+
let argument = StructureDependency(property1: "48")
18+
let resolvedDependency: DependencyWithValueTypeParameter = await container.resolve(argument: argument)
19+
20+
XCTAssertEqual(argument, resolvedDependency.subDependency, "Container returned dependency with different argument")
21+
}
22+
23+
func testRegistrationWithExplicitType() async {
24+
await container.register(type: DependencyWithValueTypeParameter.self) { (resolver, argument) in
25+
DependencyWithValueTypeParameter(subDependency: argument)
26+
}
27+
28+
let argument = StructureDependency(property1: "48")
29+
let resolvedDependency: DependencyWithValueTypeParameter = await container.resolve(argument: argument)
30+
31+
XCTAssertEqual(argument, resolvedDependency.subDependency, "Container returned dependency with different argument")
32+
}
33+
34+
func testUnmatchingArgumentType() async {
35+
await container.register { (resolver, argument) -> DependencyWithValueTypeParameter in
36+
DependencyWithValueTypeParameter(subDependency: argument)
37+
}
38+
39+
let argument = 48
40+
41+
do {
42+
_ = try await container.tryResolve(type: DependencyWithValueTypeParameter.self, argument: argument)
43+
44+
XCTFail("Expected to throw error")
45+
} catch {
46+
guard let resolutionError = error as? ResolutionError else {
47+
XCTFail("Incorrect error type")
48+
return
49+
}
50+
51+
switch resolutionError {
52+
case .unmatchingArgumentType:
53+
XCTAssertNotEqual(resolutionError.localizedDescription, "", "Error description is empty")
54+
default:
55+
XCTFail("Incorrect resolution error")
56+
}
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)