Skip to content

Commit f6bacd3

Browse files
mbrandonwhyperspacemarkCzajnikowskistephencelis
authored
Fail if testValue is invoked without providing implementation (#1399)
* Update example to set badge to the unread count (#1391) * Add store.finish(). * Fix the CaseStudies (UIKit) (#1392) * Fix warnings introduced in Xcode 14.1 (#1388) * Fix warnings introduced in Xcode 14.1 * wip * Fail when accessing testValue when one hasn't been provided. * wip * wip * wip * test Co-authored-by: Mark Adams <[email protected]> Co-authored-by: Maciek Czarnik <[email protected]> Co-authored-by: Stephen Celis <[email protected]>
1 parent 08ca5ca commit f6bacd3

File tree

7 files changed

+176
-48
lines changed

7 files changed

+176
-48
lines changed

ComposableArchitecture.xcworkspace/xcshareddata/xcschemes/Dependencies.xcscheme

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@
2828
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
2929
shouldUseLaunchSchemeArgsEnv = "YES">
3030
<Testables>
31+
<TestableReference
32+
skipped = "NO">
33+
<BuildableReference
34+
BuildableIdentifier = "primary"
35+
BlueprintIdentifier = "DependenciesTests"
36+
BuildableName = "DependenciesTests"
37+
BlueprintName = "DependenciesTests"
38+
ReferencedContainer = "container:">
39+
</BuildableReference>
40+
</TestableReference>
3141
</Testables>
3242
</TestAction>
3343
<LaunchAction

Package.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ let package = Package(
6060
.product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
6161
]
6262
),
63+
.testTarget(
64+
name: "DependenciesTests",
65+
dependencies: [
66+
"ComposableArchitecture",
67+
"Dependencies"
68+
]
69+
),
6370
.executableTarget(
6471
name: "swift-composable-architecture-benchmark",
6572
dependencies: [

Sources/Dependencies/DependencyKey.swift

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import XCTestDynamicOverlay
2+
13
/// A key for accessing dependencies.
24
///
35
/// Similar to SwiftUI's `EnvironmentKey` protocol, which is used to extend `EnvironmentValues` with
@@ -20,14 +22,6 @@ public protocol DependencyKey: TestDependencyKey {
2022
static var liveValue: Value { get }
2123
}
2224

23-
extension DependencyKey {
24-
/// A default implementation that provides the ``liveValue`` to Xcode previews.
25-
public static var previewValue: Value { Self.liveValue }
26-
27-
/// A default implementation that provides the ``liveValue`` to tests.
28-
public static var testValue: Value { Self.liveValue }
29-
}
30-
3125
/// A "test" key for accessing dependencies.
3226
///
3327
/// This protocol lives one layer below ``DependencyKey`` and allows you to separate a dependency's
@@ -67,6 +61,42 @@ public protocol TestDependencyKey {
6761
static var testValue: Value { get }
6862
}
6963

64+
extension DependencyKey {
65+
/// A default implementation that provides the ``liveValue`` to Xcode previews.
66+
public static var previewValue: Value { Self.liveValue }
67+
68+
/// A default implementation that provides the ``liveValue`` to tests.
69+
public static var testValue: Value {
70+
let dependencyDescription: String
71+
if Self.self == Value.self {
72+
dependencyDescription = """
73+
Dependency:
74+
\(typeName(Value.self))
75+
"""
76+
} else {
77+
dependencyDescription = """
78+
Key:
79+
\(typeName(Self.self))
80+
Dependency:
81+
\(typeName(Value.self))
82+
"""
83+
}
84+
85+
XCTFail("""
86+
A dependency is being used in a test environment without providing a test implementation:
87+
88+
\(dependencyDescription)
89+
90+
Dependencies registered with the library are not allowed to use their live implementations \
91+
when run in a 'TestStore'.
92+
93+
To fix, make sure that \(typeName(Self.self)) provides an implementation of 'testValue' \
94+
in its conformance to the 'DependencyKey` protocol.
95+
""")
96+
return Self.previewValue
97+
}
98+
}
99+
70100
extension TestDependencyKey {
71101
/// A default implementation that provides the ``testValue`` to Xcode previews.
72102
public static var previewValue: Value { Self.testValue }

Sources/Dependencies/DependencyValues.swift

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,29 +116,41 @@ public struct DependencyValues: Sendable {
116116
guard let value = _liveValue(Key.self) as? Key.Value
117117
else {
118118
if !Self.isSetting {
119+
let dependencyDescription: String
120+
if Key.self == Key.Value.self {
121+
dependencyDescription = """
122+
Dependency:
123+
\(typeName(Key.Value.self))
124+
"""
125+
} else {
126+
dependencyDescription = """
127+
Key:
128+
\(typeName(Key.self))
129+
Dependency:
130+
\(typeName(Key.Value.self))
131+
"""
132+
}
133+
119134
runtimeWarning(
120135
"""
121136
A dependency at %@:%d is being used in a live environment without providing a live \
122137
implementation:
123138
124-
Key:
125-
%@
126-
Dependency:
127-
%@
139+
%@
128140
129141
Every dependency registered with the library must conform to 'DependencyKey', and \
130142
that conformance must be visible to the running application.
131143
132144
To fix, make sure that '%@' conforms to 'DependencyKey' by providing a live \
133-
implementation of your dependency, and make sure that the conformance is linked with \
134-
this current application.
145+
implementation of your dependency, and make sure that the conformance is linked \
146+
with this current application.
135147
""",
136148
[
137149
"\(fileID)",
138150
line,
151+
dependencyDescription,
139152
typeName(Key.self),
140153
typeName(Key.Value.self),
141-
typeName(Key.self),
142154
],
143155
file: file,
144156
line: line

Tests/ComposableArchitectureTests/DependencyKeyTests.swift

Lines changed: 0 additions & 33 deletions
This file was deleted.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import ComposableArchitecture
2+
import XCTest
3+
4+
final class DependencyKeyTests: XCTestCase {
5+
func testTestDependencyKeyCascading() {
6+
enum Key: TestDependencyKey {
7+
static let testValue = 42
8+
}
9+
10+
XCTAssertEqual(42, Key.previewValue)
11+
XCTAssertEqual(42, Key.testValue)
12+
}
13+
14+
func testDependencyKeyCascading_ImplementOnlyLiveValue() {
15+
struct Dependency: DependencyKey {
16+
let value: Int
17+
static let liveValue = Self(value: 42)
18+
}
19+
20+
XCTAssertEqual(42, Dependency.liveValue.value)
21+
XCTAssertEqual(42, Dependency.previewValue.value)
22+
23+
XCTExpectFailure {
24+
XCTAssertEqual(42, Dependency.testValue.value)
25+
} issueMatcher: { issue in
26+
issue.compactDescription == """
27+
A dependency is being used in a test environment without providing a test implementation:
28+
29+
Dependency:
30+
DependencyKeyTests.Dependency
31+
32+
Dependencies registered with the library are not allowed to use their live implementations \
33+
when run in a 'TestStore'.
34+
35+
To fix, make sure that DependencyKeyTests.Dependency provides an implementation of \
36+
'testValue' in its conformance to the 'DependencyKey` protocol.
37+
"""
38+
}
39+
}
40+
41+
func testDependencyKeyCascading_ImplementOnlyLiveAndPreviewValue() {
42+
enum Key: DependencyKey {
43+
static let liveValue = 42
44+
static let previewValue = 1729
45+
}
46+
47+
XCTAssertEqual(42, Key.liveValue)
48+
XCTAssertEqual(1729, Key.previewValue)
49+
50+
XCTExpectFailure {
51+
XCTAssertEqual(1729, Key.testValue)
52+
} issueMatcher: { issue in
53+
issue.compactDescription == """
54+
A dependency is being used in a test environment without providing a test implementation:
55+
56+
Key:
57+
DependencyKeyTests.Key
58+
Dependency:
59+
Int
60+
61+
Dependencies registered with the library are not allowed to use their live implementations \
62+
when run in a 'TestStore'.
63+
64+
To fix, make sure that DependencyKeyTests.Key provides an implementation of 'testValue' in \
65+
its conformance to the 'DependencyKey` protocol.
66+
"""
67+
}
68+
}
69+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import ComposableArchitecture
2+
import XCTest
3+
4+
final class DependencyValuesTests: XCTestCase {
5+
func testMissingLiveValue() {
6+
enum Key: TestDependencyKey {
7+
static let testValue = 42
8+
}
9+
10+
var line = 0
11+
XCTExpectFailure {
12+
$0.compactDescription == """
13+
A dependency at DependenciesTests/DependencyValuesTests.swift:\(line) is being used in a \
14+
live environment without providing a live implementation:
15+
16+
Key:
17+
DependencyValuesTests.Key
18+
Dependency:
19+
Int
20+
21+
Every dependency registered with the library must conform to 'DependencyKey', and that \
22+
conformance must be visible to the running application.
23+
24+
To fix, make sure that 'DependencyValuesTests.Key' conforms to 'DependencyKey' by \
25+
providing a live implementation of your dependency, and make sure that the conformance is \
26+
linked with this current application.
27+
"""
28+
}
29+
30+
line = #line + 1
31+
_ = DependencyValues.current[Key.self]
32+
}
33+
}

0 commit comments

Comments
 (0)