Skip to content

Commit f00277a

Browse files
authored
Tighten TestStore equality expectations (#1091)
* Add tests for TestStore equality assertions * Change TestStore to require a state change if a closure is given * Update a use of TestStore that described a change unnecessarily * Remove unused mainQueue
1 parent 65401c3 commit f00277a

File tree

3 files changed

+124
-5
lines changed

3 files changed

+124
-5
lines changed

Sources/ComposableArchitecture/TestSupport/TestStore.swift

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@
341341
_ action: LocalAction,
342342
file: StaticString = #file,
343343
line: UInt = #line,
344-
_ update: @escaping (inout LocalState) throws -> Void = { _ in }
344+
_ update: ((inout LocalState) throws -> Void)? = nil
345345
) {
346346
if !self.receivedActions.isEmpty {
347347
var actions = ""
@@ -359,7 +359,12 @@
359359
var expectedState = self.toLocalState(self.snapshotState)
360360
self.store.send(.init(origin: .send(action), file: file, line: line))
361361
do {
362-
try update(&expectedState)
362+
try self.expectedStateShouldChange(
363+
expected: &expectedState,
364+
update: update,
365+
file: file,
366+
line: line
367+
)
363368
} catch {
364369
XCTFail("Threw error: \(error)", file: file, line: line)
365370
}
@@ -374,6 +379,27 @@
374379
}
375380
}
376381

382+
private func expectedStateShouldChange(
383+
expected: inout LocalState,
384+
update: ((inout LocalState) throws -> Void)? = nil,
385+
file: StaticString,
386+
line: UInt
387+
) throws {
388+
guard let update = update else { return }
389+
let current = expected
390+
try update(&expected)
391+
if expected == current {
392+
XCTFail(
393+
"""
394+
Expected to modify the expected state, but no change occurred.
395+
396+
Ensure that the state was modified or remove the closure to assert no change.
397+
""",
398+
file: file, line: line
399+
)
400+
}
401+
}
402+
377403
private func expectedStateShouldMatch(
378404
expected: LocalState,
379405
actual: LocalState,
@@ -410,7 +436,7 @@
410436
_ expectedAction: Action,
411437
file: StaticString = #file,
412438
line: UInt = #line,
413-
_ update: @escaping (inout LocalState) throws -> Void = { _ in }
439+
_ update: ((inout LocalState) throws -> Void)? = nil
414440
) {
415441
guard !self.receivedActions.isEmpty else {
416442
XCTFail(
@@ -445,7 +471,12 @@
445471
}
446472
var expectedState = self.toLocalState(self.snapshotState)
447473
do {
448-
try update(&expectedState)
474+
try self.expectedStateShouldChange(
475+
expected: &expectedState,
476+
update: update,
477+
file: file,
478+
line: line
479+
)
449480
} catch {
450481
XCTFail("Threw error: \(error)", file: file, line: line)
451482
}

Tests/ComposableArchitectureTests/ComposableArchitectureTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ final class ComposableArchitectureTests: XCTestCase {
163163

164164
store.send(.incr) { $0 = 1 }
165165
scheduler.advance()
166-
store.receive(.response(1)) { $0 = 1 }
166+
store.receive(.response(1))
167167

168168
store.send(.incr) { $0 = 2 }
169169
store.send(.cancel)

Tests/ComposableArchitectureTests/TestStoreTests.swift

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,92 @@ class TestStoreTests: XCTestCase {
5959

6060
store.send(.d)
6161
}
62+
func testExpectedStateEquality() {
63+
struct State: Equatable {
64+
var count: Int = 0
65+
var isChanging: Bool = false
66+
}
67+
68+
enum Action: Equatable {
69+
case increment, changed(from: Int, to: Int)
70+
}
71+
72+
let reducer = Reducer<State, Action, Void> { state, action, scheduler in
73+
switch action {
74+
case .increment:
75+
state.isChanging = true
76+
return Effect(value: .changed(from: state.count, to: state.count + 1))
77+
case .changed(let from, let to):
78+
state.isChanging = false
79+
if state.count == from {
80+
state.count = to
81+
}
82+
return .none
83+
}
84+
}
85+
86+
let store = TestStore(
87+
initialState: State(),
88+
reducer: reducer,
89+
environment: ()
90+
)
91+
92+
store.send(.increment) {
93+
$0.isChanging = true
94+
}
95+
store.receive(.changed(from: 0, to: 1)) {
96+
$0.isChanging = false
97+
$0.count = 1
98+
}
99+
100+
XCTExpectFailure {
101+
store.send(.increment) {
102+
$0.isChanging = false
103+
}
104+
}
105+
XCTExpectFailure {
106+
store.receive(.changed(from: 1, to: 2)) {
107+
$0.isChanging = true
108+
$0.count = 1100
109+
}
110+
}
111+
}
112+
func testExpectedStateEqualityMustModify() {
113+
struct State: Equatable {
114+
var count: Int = 0
115+
}
116+
117+
enum Action: Equatable {
118+
case noop, finished
119+
}
120+
121+
let reducer = Reducer<State, Action, Void> { state, action, scheduler in
122+
switch action {
123+
case .noop:
124+
return Effect(value: .finished)
125+
case .finished:
126+
return .none
127+
}
128+
}
129+
130+
let store = TestStore(
131+
initialState: State(),
132+
reducer: reducer,
133+
environment: ()
134+
)
135+
136+
store.send(.noop)
137+
store.receive(.finished)
138+
139+
XCTExpectFailure {
140+
store.send(.noop) {
141+
$0.count = 0
142+
}
143+
}
144+
XCTExpectFailure {
145+
store.receive(.finished) {
146+
$0.count = 0
147+
}
148+
}
149+
}
62150
}

0 commit comments

Comments
 (0)