Skip to content

Commit 8741cd9

Browse files
rcarvermluisbrown
authored andcommitted
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 87778b7 commit 8741cd9

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
@@ -337,7 +337,7 @@
337337
_ action: LocalAction,
338338
file: StaticString = #file,
339339
line: UInt = #line,
340-
_ update: @escaping (inout LocalState) throws -> Void = { _ in }
340+
_ update: ((inout LocalState) throws -> Void)? = nil
341341
) {
342342
if !self.receivedActions.isEmpty {
343343
var actions = ""
@@ -355,7 +355,12 @@
355355
var expectedState = self.toLocalState(self.snapshotState)
356356
self.store.send(.init(origin: .send(action), file: file, line: line))
357357
do {
358-
try update(&expectedState)
358+
try self.expectedStateShouldChange(
359+
expected: &expectedState,
360+
update: update,
361+
file: file,
362+
line: line
363+
)
359364
} catch {
360365
XCTFail("Threw error: \(error)", file: file, line: line)
361366
}
@@ -370,6 +375,27 @@
370375
}
371376
}
372377

378+
private func expectedStateShouldChange(
379+
expected: inout LocalState,
380+
update: ((inout LocalState) throws -> Void)? = nil,
381+
file: StaticString,
382+
line: UInt
383+
) throws {
384+
guard let update = update else { return }
385+
let current = expected
386+
try update(&expected)
387+
if expected == current {
388+
XCTFail(
389+
"""
390+
Expected to modify the expected state, but no change occurred.
391+
392+
Ensure that the state was modified or remove the closure to assert no change.
393+
""",
394+
file: file, line: line
395+
)
396+
}
397+
}
398+
373399
private func expectedStateShouldMatch(
374400
expected: LocalState,
375401
actual: LocalState,
@@ -406,7 +432,7 @@
406432
_ expectedAction: Action,
407433
file: StaticString = #file,
408434
line: UInt = #line,
409-
_ update: @escaping (inout LocalState) throws -> Void = { _ in }
435+
_ update: ((inout LocalState) throws -> Void)? = nil
410436
) {
411437
guard !self.receivedActions.isEmpty else {
412438
XCTFail(
@@ -441,7 +467,12 @@
441467
}
442468
var expectedState = self.toLocalState(self.snapshotState)
443469
do {
444-
try update(&expectedState)
470+
try self.expectedStateShouldChange(
471+
expected: &expectedState,
472+
update: update,
473+
file: file,
474+
line: line
475+
)
445476
} catch {
446477
XCTFail("Threw error: \(error)", file: file, line: line)
447478
}

Tests/ComposableArchitectureTests/ComposableArchitectureTests.swift

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

151151
store.send(.incr) { $0 = 1 }
152152
scheduler.advance()
153-
store.receive(.response(1)) { $0 = 1 }
153+
store.receive(.response(1))
154154

155155
store.send(.incr) { $0 = 2 }
156156
store.send(.cancel)

Tests/ComposableArchitectureTests/TestStoreTests.swift

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

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

0 commit comments

Comments
 (0)