Skip to content

Commit 12bdb89

Browse files
authored
Remove Equatable Requirement on Actions (#1)
* Remove the requirement on Equatable Actions. (BREAKING CHANGE) * Use AutoClosure for Actions so that everything is evaluated lazy * Include the Step information in asserts for better debugging
1 parent 4a767d4 commit 12bdb89

File tree

1 file changed

+75
-25
lines changed

1 file changed

+75
-25
lines changed

Sources/TestingExtensions/UseCaseTests.swift

Lines changed: 75 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,59 @@ import Foundation
1212
@testable import SwiftRex
1313
import XCTest
1414

15-
public enum Step<ActionType, StateType> {
16-
case send(
17-
ActionType,
18-
file: StaticString = #file,
19-
line: UInt = #line,
20-
stateChange: (inout StateType) -> Void = { _ in }
21-
)
22-
case receive(
23-
ActionType,
15+
public struct SendStep<ActionType, StateType> {
16+
public init(
17+
action: @autoclosure @escaping () -> ActionType,
2418
file: StaticString = #file,
2519
line: UInt = #line,
26-
stateChange: (inout StateType) -> Void = { _ in }
27-
)
20+
stateChange: @escaping (inout StateType) -> Void = { _ in }
21+
) {
22+
self.action = action
23+
self.file = file
24+
self.line = line
25+
self.stateChange = stateChange
26+
}
27+
28+
let action: () -> ActionType
29+
let file: StaticString
30+
let line: UInt
31+
let stateChange: (inout StateType) -> Void
32+
}
33+
34+
public struct ReceiveStep<ActionType, StateType> {
35+
public init(isExpectedAction: @escaping (ActionType) -> Bool,
36+
file: StaticString = #file,
37+
line: UInt = #line,
38+
stateChange: @escaping (inout StateType) -> Void = { _ in }) {
39+
self.isExpectedAction = isExpectedAction
40+
self.file = file
41+
self.line = line
42+
self.stateChange = stateChange
43+
}
44+
45+
public init(action: @autoclosure @escaping () -> ActionType,
46+
file: StaticString = #file,
47+
line: UInt = #line,
48+
stateChange: @escaping (inout StateType) -> Void = { _ in }
49+
) where ActionType: Equatable {
50+
self.init(
51+
isExpectedAction: { $0 == action() },
52+
file: file,
53+
line: line,
54+
stateChange: stateChange
55+
)
56+
}
57+
58+
let file: StaticString
59+
let line: UInt
60+
let stateChange: (inout StateType) -> Void
61+
62+
let isExpectedAction: (ActionType) -> Bool
63+
}
64+
65+
public enum Step<ActionType, StateType> {
66+
case send(SendStep<ActionType, StateType>)
67+
case receive(ReceiveStep<ActionType, StateType>)
2868
case sideEffectResult(
2969
do: () -> Void
3070
)
@@ -39,7 +79,7 @@ extension XCTestCase {
3979
otherSteps: [Step<M.InputActionType, M.StateType>] = [],
4080
file: StaticString = #filePath,
4181
line: UInt = #line
42-
) where M.InputActionType == M.OutputActionType, M.InputActionType: Equatable, M.StateType: Equatable {
82+
) where M.InputActionType == M.OutputActionType, M.StateType: Equatable {
4383
assert(
4484
initialValue: initialValue,
4585
reducer: reducer,
@@ -60,7 +100,7 @@ extension XCTestCase {
60100
stateEquating: (M.StateType, M.StateType) -> Bool,
61101
file: StaticString = #filePath,
62102
line: UInt = #line
63-
) where M.InputActionType == M.OutputActionType, M.InputActionType: Equatable {
103+
) where M.InputActionType == M.OutputActionType {
64104
assert(
65105
initialValue: initialValue,
66106
reducer: reducer,
@@ -80,7 +120,7 @@ extension XCTestCase {
80120
stateEquating: (M.StateType, M.StateType) -> Bool,
81121
file: StaticString = #filePath,
82122
line: UInt = #line
83-
) where M.InputActionType == M.OutputActionType, M.InputActionType: Equatable {
123+
) where M.InputActionType == M.OutputActionType {
84124
var state = initialValue
85125
var middlewareResponses: [M.OutputActionType] = []
86126
let gotAction = XCTestExpectation(description: "got action")
@@ -91,16 +131,21 @@ extension XCTestCase {
91131
}
92132
middleware.receiveContext(getState: { state }, output: anyActionHandler)
93133

94-
steps.forEach { step in
134+
steps.forEach { outerStep in
95135
var expected = state
96136

97-
switch step {
98-
case let .send(action, file, line, stateChange):
137+
switch outerStep {
138+
case let .send(step)://action, file, line, stateChange):
139+
let file = step.file
140+
let line = step.line
141+
let stateChange = step.stateChange
142+
99143
if !middlewareResponses.isEmpty {
100144
XCTFail("Action sent before handling \(middlewareResponses.count) pending effect(s)", file: file, line: line)
101145
}
102146

103147
var afterReducer: AfterReducer = .doNothing()
148+
let action = step.action()
104149
middleware.handle(
105150
action: action,
106151
from: .init(file: "\(file)", function: "", line: line, info: nil),
@@ -110,8 +155,12 @@ extension XCTestCase {
110155
afterReducer.reducerIsDone()
111156

112157
stateChange(&expected)
113-
ensureStateMutation(equating: stateEquating, statusQuo: state, expected: expected)
114-
case let .receive(action, file, line, stateChange):
158+
ensureStateMutation(equating: stateEquating, statusQuo: state, expected: expected, step: outerStep)
159+
case let .receive(step)://action, file, line, stateChange):
160+
let file = step.file
161+
let line = step.line
162+
let stateChange = step.stateChange
163+
115164
if middlewareResponses.isEmpty {
116165
_ = XCTWaiter.wait(for: [gotAction], timeout: 0.2)
117166
}
@@ -120,19 +169,19 @@ extension XCTestCase {
120169
break
121170
}
122171
let first = middlewareResponses.removeFirst()
123-
XCTAssertEqual(first, action, file: file, line: line)
172+
XCTAssertTrue(step.isExpectedAction(first), file: file, line: line)
124173

125174
var afterReducer: AfterReducer = .doNothing()
126175
middleware.handle(
127-
action: action,
176+
action: first,
128177
from: .init(file: "\(file)", function: "", line: line, info: nil),
129178
afterReducer: &afterReducer
130179
)
131-
reducer.reduce(action, &state)
180+
reducer.reduce(first, &state)
132181
afterReducer.reducerIsDone()
133182

134183
stateChange(&expected)
135-
ensureStateMutation(equating: stateEquating, statusQuo: state, expected: expected)
184+
ensureStateMutation(equating: stateEquating, statusQuo: state, expected: expected, step: outerStep)
136185
case let .sideEffectResult(execute):
137186
execute()
138187
}
@@ -144,10 +193,11 @@ extension XCTestCase {
144193
}
145194
}
146195

147-
private func ensureStateMutation<StateType>(
196+
private func ensureStateMutation<ActionType, StateType>(
148197
equating: (StateType, StateType) -> Bool,
149198
statusQuo: StateType,
150199
expected: StateType,
200+
step: Step<ActionType, StateType>,
151201
file: StaticString = #filePath,
152202
line: UInt = #line
153203
) {
@@ -159,7 +209,7 @@ extension XCTestCase {
159209
dump(expected, to: &expectedString, name: nil, indent: 2)
160210
let difference = diff(old: expectedString, new: stateString) ?? ""
161211

162-
return "Expected state different from current state\n\(difference)"
212+
return "Expected state after step \(step) different from current state\n\(difference)"
163213
}(),
164214
file: file,
165215
line: line

0 commit comments

Comments
 (0)