From cb90b3730dfe623d07c8e0a64e4a93c6500ecac8 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Tue, 11 Apr 2023 00:41:07 +0000 Subject: [PATCH] [Libraries] Dependency (#6) This PR contains the work that implements the Dependency service, which is used as a dependency injection mechanism in the application. To give further details on what was done: - [x] created the `Dependency` library into the **Libraries** package; - [x] defined the `DependencyKey` protocol; - [x] implemented the `DependencyService` service; - [x] implemented the `Dependency` property wrapper. Co-authored-by: Javier Cicchelli Reviewed-on: https://repo.rock-n-code.com/rock-n-code/deep-linking-assignment/pulls/6 --- Apps/Locations/Libraries/Package.swift | 14 +++- .../Property Wrappers/Dependency.swift | 31 ++++++++ .../Dependency/Protocols/DependencyKey.swift | 22 ++++++ .../Services/DependencyService.swift | 28 +++++++ .../Helpers/TestServices.swift | 34 +++++++++ .../Property Wrappers/DependencyTests.swift | 74 +++++++++++++++++++ .../Services/DependencyServiceTests.swift | 58 +++++++++++++++ 7 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 Apps/Locations/Libraries/Sources/Dependency/Property Wrappers/Dependency.swift create mode 100644 Apps/Locations/Libraries/Sources/Dependency/Protocols/DependencyKey.swift create mode 100644 Apps/Locations/Libraries/Sources/Dependency/Services/DependencyService.swift create mode 100644 Apps/Locations/Libraries/Tests/DependencyTests/Helpers/TestServices.swift create mode 100644 Apps/Locations/Libraries/Tests/DependencyTests/Property Wrappers/DependencyTests.swift create mode 100644 Apps/Locations/Libraries/Tests/DependencyTests/Services/DependencyServiceTests.swift diff --git a/Apps/Locations/Libraries/Package.swift b/Apps/Locations/Libraries/Package.swift index 396d480..6c5bf79 100644 --- a/Apps/Locations/Libraries/Package.swift +++ b/Apps/Locations/Libraries/Package.swift @@ -11,7 +11,9 @@ let package = Package( .library( name: "Libraries", targets: [ - "Locations" + "Dependency", + "Locations", + "Persistence" ] ), ], @@ -21,6 +23,10 @@ let package = Package( name: "APICore", dependencies: [] ), + .target( + name: "Dependency", + dependencies: [] + ), .target( name: "Locations", dependencies: [ @@ -37,6 +43,12 @@ let package = Package( "APICore" ] ), + .testTarget( + name: "DependencyTests", + dependencies: [ + "Dependency" + ] + ), .testTarget( name: "LocationsTests", dependencies: [ diff --git a/Apps/Locations/Libraries/Sources/Dependency/Property Wrappers/Dependency.swift b/Apps/Locations/Libraries/Sources/Dependency/Property Wrappers/Dependency.swift new file mode 100644 index 0000000..83b9eda --- /dev/null +++ b/Apps/Locations/Libraries/Sources/Dependency/Property Wrappers/Dependency.swift @@ -0,0 +1,31 @@ +// +// Dependency.swift +// Dependency +// +// Created by Javier Cicchelli on 11/04/2023. +// Copyright © 2023 Röck+Cöde. All rights reserved. +// + +/// This property wrapper provides a direct connection to the `DependencyService` service. +@propertyWrapper +public struct Dependency { + + // MARK: Properties + + private let keyPath: WritableKeyPath + + /// This property allows direct read/write access to a defined dependency attached to a selected key path. + public var wrappedValue: D { + get { DependencyService[keyPath] } + set { DependencyService[keyPath] = newValue } + } + + // MARK: Initialisers + + /// Initialise the property wrapper by setting a key path to a defined dependency. + /// - Parameter keyPath: A key path to a defined dependency in the `DependencyService` service. + public init(_ keyPath: WritableKeyPath) { + self.keyPath = keyPath + } + +} diff --git a/Apps/Locations/Libraries/Sources/Dependency/Protocols/DependencyKey.swift b/Apps/Locations/Libraries/Sources/Dependency/Protocols/DependencyKey.swift new file mode 100644 index 0000000..8f5fb08 --- /dev/null +++ b/Apps/Locations/Libraries/Sources/Dependency/Protocols/DependencyKey.swift @@ -0,0 +1,22 @@ +// +// DependencyKey.swift +// Dependency +// +// Created by Javier Cicchelli on 11/04/2023. +// Copyright © 2023 Röck+Cöde. All rights reserved. +// + +/// This protocol defines a key to use in the dependency service. +public protocol DependencyKey { + + // MARK: Associated types + + /// The associated type representing the type of the dependency key's value. + associatedtype Value + + // MARK: Properties + + /// The default value for the dependency key. + static var currentValue: Value { get set } + +} diff --git a/Apps/Locations/Libraries/Sources/Dependency/Services/DependencyService.swift b/Apps/Locations/Libraries/Sources/Dependency/Services/DependencyService.swift new file mode 100644 index 0000000..3716c42 --- /dev/null +++ b/Apps/Locations/Libraries/Sources/Dependency/Services/DependencyService.swift @@ -0,0 +1,28 @@ +// +// DependencyService.swift +// Dependency +// +// Created by Javier Cicchelli on 11/04/2023. +// Copyright © 2023 Röck+Cöde. All rights reserved. +// + +/// This service provide write/read access to the injected dependencies. +public struct DependencyService { + + // MARK: Properties + + private static var current = DependencyService() + + // MARK: Subscripts + + public static subscript(key: DK.Type) -> DK.Value { + get { key.currentValue } + set { key.currentValue = newValue } + } + + public static subscript(_ keyPath: WritableKeyPath) -> D { + get { current[keyPath: keyPath] } + set { current[keyPath: keyPath] = newValue } + } + +} diff --git a/Apps/Locations/Libraries/Tests/DependencyTests/Helpers/TestServices.swift b/Apps/Locations/Libraries/Tests/DependencyTests/Helpers/TestServices.swift new file mode 100644 index 0000000..2f19465 --- /dev/null +++ b/Apps/Locations/Libraries/Tests/DependencyTests/Helpers/TestServices.swift @@ -0,0 +1,34 @@ +// +// TestServices.swift +// DependencyTests +// +// Created by Javier Cicchelli on 11/04/2023. +// Copyright © 2023 Röck+Cöde. All rights reserved. +// + +import Dependency + +// MARK: - Protocols + +protocol TestService {} + +// MARK: - Services + +struct SomeService: TestService, Equatable {} +struct SomeOtherService: TestService, Equatable {} + +// MARK: - DependencyKey + +struct TestServiceKey: DependencyKey { + static var currentValue: TestService = SomeService() +} + +// MARK: - DependencyService+Keys + +extension DependencyService { + var testService: TestService { + get { Self[TestServiceKey.self] } + set { Self[TestServiceKey.self] = newValue } + } +} + diff --git a/Apps/Locations/Libraries/Tests/DependencyTests/Property Wrappers/DependencyTests.swift b/Apps/Locations/Libraries/Tests/DependencyTests/Property Wrappers/DependencyTests.swift new file mode 100644 index 0000000..7daf5ab --- /dev/null +++ b/Apps/Locations/Libraries/Tests/DependencyTests/Property Wrappers/DependencyTests.swift @@ -0,0 +1,74 @@ +// +// DependencyTests.swift +// DependencyTests +// +// Created by Javier Cicchelli on 11/04/2023. +// Copyright © 2023 Röck+Cöde. All rights reserved. +// + +import XCTest + +@testable import Dependency + +final class DependencyTests: XCTestCase { + + // MARK: Properties + + private var subject: TestSubject! + + // MARK: Setup + + override func setUp() { + DependencyService[\.testService] = SomeService() + } + + // MARK: Tests + + func test_readTestService() { + // GIVEN + subject = .init() + + // WHEN + let service = subject.testService + + // THEN + XCTAssertNotNil(service) + XCTAssert(service is SomeService) + } + + func test_writeDependencyKey() async throws { + // GIVEN + subject = .init() + + subject.testService = SomeOtherService() + + // WHEN + let service = DependencyService[\.testService] + + // THEN + XCTAssertNotNil(service) + XCTAssert(service is SomeOtherService) + } + + func test_writeDependencyKeyTwice() async throws { + // GIVEN + subject = .init() + + subject.testService = SomeOtherService() + subject.testService = SomeService() + + // WHEN + let service = DependencyService[\.testService] + + // THEN + XCTAssertNotNil(service) + XCTAssert(service is SomeService) + } + +} + +// MARK: - TestSubject + +private struct TestSubject { + @Dependency(\.testService) var testService +} diff --git a/Apps/Locations/Libraries/Tests/DependencyTests/Services/DependencyServiceTests.swift b/Apps/Locations/Libraries/Tests/DependencyTests/Services/DependencyServiceTests.swift new file mode 100644 index 0000000..186931a --- /dev/null +++ b/Apps/Locations/Libraries/Tests/DependencyTests/Services/DependencyServiceTests.swift @@ -0,0 +1,58 @@ +// +// DependencyServiceTests.swift +// DependencyTests +// +// Created by Javier Cicchelli on 11/04/2023. +// Copyright © 2023 Röck+Cöde. All rights reserved. +// + +import XCTest + +@testable import Dependency + +final class DependencyServiceTests: XCTestCase { + + // MARK: Setup + + override func setUp() { + DependencyService[\.testService] = SomeService() + } + + // MARK: Tests + + func test_readDependencyKey() async throws { + // GIVEN + // WHEN + let service = DependencyService[\.testService] + + // THEN + XCTAssertNotNil(service) + XCTAssert(service is SomeService) + } + + func test_writeDependencyKey() async throws { + // GIVEN + DependencyService[\.testService] = SomeOtherService() + + // WHEN + let service = DependencyService[\.testService] + + // THEN + XCTAssertNotNil(service) + XCTAssert(service is SomeOtherService) + } + + func test_writeDependencyKeyTwice() async throws { + // GIVEN + DependencyService[\.testService] = SomeOtherService() + DependencyService[\.testService] = SomeService() + + // WHEN + let service = DependencyService[\.testService] + + // THEN + XCTAssertNotNil(service) + XCTAssert(service is SomeService) + } + +}