Skip to content

Commit f8554f7

Browse files
Avoid failure when setting test dependency values (#1570)
* Avoid failure when setting test dependency values It's currently possible to emit XCTest failures when testing reducers that access dependency values that emit failure upon access, which includes the calendar, locale, time zone, and URL session dependencies. We may need a more holistic solution for folks that define these kinds of dependencies outside the library, but this will make the library-level failures a bit more lenient. * wip * a few more tests Co-authored-by: Brandon Williams <[email protected]>
1 parent 7aae723 commit f8554f7

File tree

8 files changed

+101
-6
lines changed

8 files changed

+101
-6
lines changed

Examples/TicTacToe/tic-tac-toe/Sources/AuthenticationClient/AuthenticationClient.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public struct AuthenticationClient: Sendable {
7272
}
7373

7474
extension AuthenticationClient: TestDependencyKey {
75-
public static var testValue = Self(
75+
public static let testValue = Self(
7676
login: unimplemented("\(Self.self).login"),
7777
twoFactor: unimplemented("\(Self.self).twoFactor")
7878
)

Sources/Dependencies/Dependencies/Calendar.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ extension DependencyValues {
3333
private enum CalendarKey: DependencyKey {
3434
static let liveValue = Calendar.autoupdatingCurrent
3535
static var testValue: Calendar {
36-
XCTFail(#"Unimplemented: @Dependency(\.calendar)"#)
36+
if !DependencyValues.isSetting {
37+
XCTFail(#"Unimplemented: @Dependency(\.calendar)"#)
38+
}
3739
return .autoupdatingCurrent
3840
}
3941
}

Sources/Dependencies/Dependencies/Locale.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ extension DependencyValues {
4545
private enum LocaleKey: DependencyKey {
4646
static let liveValue = Locale.autoupdatingCurrent
4747
static var testValue: Locale {
48-
XCTFail(#"Unimplemented: @Dependency(\.locale)"#)
48+
if !DependencyValues.isSetting {
49+
XCTFail(#"Unimplemented: @Dependency(\.locale)"#)
50+
}
4951
return .autoupdatingCurrent
5052
}
5153
}

Sources/Dependencies/Dependencies/TimeZone.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ extension DependencyValues {
2424
private enum TimeZoneKey: DependencyKey {
2525
static let liveValue = TimeZone.autoupdatingCurrent
2626
static var testValue: TimeZone {
27-
XCTFail(#"Unimplemented: @Dependency(\.timeZone)"#)
27+
if !DependencyValues.isSetting {
28+
XCTFail(#"Unimplemented: @Dependency(\.timeZone)"#)
29+
}
2830
return .autoupdatingCurrent
2931
}
3032
}

Sources/Dependencies/Dependencies/URLSession.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ extension DependencyValues {
7979
private enum URLSessionKey: DependencyKey {
8080
static let liveValue = URLSession.shared
8181
static var testValue: URLSession {
82-
XCTFail(#"Unimplemented: @Dependency(\.urlSession)"#)
82+
if !DependencyValues.isSetting {
83+
XCTFail(#"Unimplemented: @Dependency(\.urlSession)"#)
84+
}
8385
let configuration = URLSessionConfiguration.ephemeral
8486
configuration.protocolClasses = [UnimplementedURLProtocol.self]
8587
return URLSession(configuration: configuration)

Sources/Dependencies/DependencyValues.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ import XCTestDynamicOverlay
5555
/// ```
5656
public struct DependencyValues: Sendable {
5757
@TaskLocal public static var _current = Self()
58-
@TaskLocal fileprivate static var isSetting = false
58+
@TaskLocal static var isSetting = false
5959
@TaskLocal static var currentDependency = CurrentDependency()
6060

6161
private var cachedValues = CachedValues()

Tests/ComposableArchitectureTests/StoreTests.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,4 +510,33 @@ final class StoreTests: XCTestCase {
510510
XCTAssertEqual(store.effectCancellables.count, 0)
511511
XCTAssertEqual(scopedStore.effectCancellables.count, 0)
512512
}
513+
514+
func testOverrideDependenciesDirectlyOnReducer() {
515+
struct Counter: ReducerProtocol {
516+
@Dependency(\.calendar) var calendar
517+
@Dependency(\.locale) var locale
518+
@Dependency(\.timeZone) var timeZone
519+
@Dependency(\.urlSession) var urlSession
520+
521+
func reduce(into state: inout Int, action: Bool) -> EffectTask<Bool> {
522+
_ = self.calendar
523+
_ = self.locale
524+
_ = self.timeZone
525+
_ = self.urlSession
526+
state += action ? 1 : -1
527+
return .none
528+
}
529+
}
530+
531+
let store = Store(
532+
initialState: 0,
533+
reducer: Counter()
534+
.dependency(\.calendar, Calendar(identifier: .gregorian))
535+
.dependency(\.locale, Locale(identifier: "en_US"))
536+
.dependency(\.timeZone, TimeZone(secondsFromGMT: 0)!)
537+
.dependency(\.urlSession, URLSession(configuration: .ephemeral))
538+
)
539+
540+
ViewStore(store).send(true)
541+
}
513542
}

Tests/ComposableArchitectureTests/TestStoreTests.swift

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,62 @@ final class TestStoreTests: XCTestCase {
206206
}
207207
XCTAssertEqual(store.state, 4)
208208
}
209+
210+
func testOverrideDependenciesDirectlyOnReducer() {
211+
struct Counter: ReducerProtocol {
212+
@Dependency(\.calendar) var calendar
213+
@Dependency(\.locale) var locale
214+
@Dependency(\.timeZone) var timeZone
215+
@Dependency(\.urlSession) var urlSession
216+
217+
func reduce(into state: inout Int, action: Bool) -> EffectTask<Bool> {
218+
_ = self.calendar
219+
_ = self.locale
220+
_ = self.timeZone
221+
_ = self.urlSession
222+
state += action ? 1 : -1
223+
return .none
224+
}
225+
}
226+
227+
let store = TestStore(
228+
initialState: 0,
229+
reducer: Counter()
230+
.dependency(\.calendar, Calendar(identifier: .gregorian))
231+
.dependency(\.locale, Locale(identifier: "en_US"))
232+
.dependency(\.timeZone, TimeZone(secondsFromGMT: 0)!)
233+
.dependency(\.urlSession, URLSession(configuration: .ephemeral))
234+
)
235+
236+
store.send(true) { $0 = 1 }
237+
}
238+
239+
func testOverrideDependenciesOnTestStore() {
240+
struct Counter: ReducerProtocol {
241+
@Dependency(\.calendar) var calendar
242+
@Dependency(\.locale) var locale
243+
@Dependency(\.timeZone) var timeZone
244+
@Dependency(\.urlSession) var urlSession
245+
246+
func reduce(into state: inout Int, action: Bool) -> EffectTask<Bool> {
247+
_ = self.calendar
248+
_ = self.locale
249+
_ = self.timeZone
250+
_ = self.urlSession
251+
state += action ? 1 : -1
252+
return .none
253+
}
254+
}
255+
256+
let store = TestStore(
257+
initialState: 0,
258+
reducer: Counter()
259+
)
260+
store.dependencies.calendar = Calendar(identifier: .gregorian)
261+
store.dependencies.locale = Locale(identifier: "en_US")
262+
store.dependencies.timeZone = TimeZone(secondsFromGMT: 0)!
263+
store.dependencies.urlSession = URLSession(configuration: .ephemeral)
264+
265+
store.send(true) { $0 = 1 }
266+
}
209267
}

0 commit comments

Comments
 (0)