Skip to content

Commit ada9392

Browse files
committed
[fix]: improve optional handling in TestApplyContext
1 parent ff4aaea commit ada9392

File tree

2 files changed

+60
-8
lines changed

2 files changed

+60
-8
lines changed

WorkflowTesting/Sources/WorkflowActionTester.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,10 +209,25 @@ struct TestApplyContext<Wrapped: Workflow>: ApplyContextType {
209209
case .workflow(let workflow):
210210
return workflow[keyPath: keyPath]
211211
case .expectations(var expectedValues):
212-
guard let value = expectedValues.removeValue(forKey: keyPath) as? Value else {
213-
fatalError("Attempted to read value \(keyPath as AnyKeyPath), when applying an action, but no value was present. Pass an instance of the Workflow to the ActionTester to enable this functionality.")
212+
guard
213+
// We have an expected value
214+
let value = expectedValues.removeValue(forKey: keyPath),
215+
// And it's the right type
216+
let value = value as? Value
217+
else {
218+
// We're expecting a value of optional type. Error, but don't crash
219+
// since we can just return nil.
220+
if Value.self is OptionalProtocol.Type {
221+
reportIssue("Attempted to read value \(keyPath as AnyKeyPath), when applying an action, but no value was present. Pass an instance of the Workflow to the ActionTester to enable this functionality.")
222+
return Any?.none as! Value
223+
} else {
224+
fatalError("Attempted to read value \(keyPath as AnyKeyPath), when applying an action, but no value was present. Pass an instance of the Workflow to the ActionTester to enable this functionality.")
225+
}
214226
}
215227
return value
216228
}
217229
}
218230
}
231+
232+
private protocol OptionalProtocol {}
233+
extension Optional: OptionalProtocol {}

WorkflowTesting/Tests/WorkflowActionTesterTests.swift

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
* limitations under the License.
1515
*/
1616

17+
import IssueReporting
1718
import Workflow
1819
import XCTest
20+
1921
@testable import WorkflowTesting
2022

2123
final class WorkflowActionTesterTests: XCTestCase {
@@ -112,14 +114,34 @@ extension WorkflowActionTesterTests {
112114
withState: true,
113115
workflow: TestWorkflow(prop: 42)
114116
)
115-
.send(action: .readProps)
117+
.send(action: .readProp)
118+
.assert(state: true)
119+
.assert(output: .value("read prop: 42"))
120+
}
121+
122+
func test_new_api_works_with_optional_props() {
123+
TestActionWithProps
124+
.tester(
125+
withState: true,
126+
workflow: TestWorkflow(prop: 42, optionalProp: 22)
127+
)
128+
.send(action: .readOptionalProp)
129+
.assert(state: true)
130+
.assert(output: .value("read optional prop: 22"))
131+
132+
TestActionWithProps
133+
.tester(
134+
withState: true,
135+
workflow: TestWorkflow(prop: 42, optionalProp: nil)
136+
)
137+
.send(action: .readOptionalProp)
116138
.assert(state: true)
117-
.assert(output: .value("read props: 42"))
139+
.assert(output: .value("read optional prop: <nil>"))
118140
}
119141

120142
// FIXME: ideally an 'exit/death test' would somehow be used for this...
121143
/*
122-
func test_old_api_explodes_if_you_use_props() {
144+
func test_old_api_explodes_if_accessing_through_apply_context() {
123145
XCTExpectFailure("This test should fail")
124146

125147
TestActionWithProps
@@ -128,14 +150,24 @@ extension WorkflowActionTesterTests {
128150
.assert(state: true)
129151
}
130152
*/
153+
154+
func test_old_api_errors_accessing_optional_through_apply_context_without_proper_setup() {
155+
withExpectedIssue("reading optional value through context without workflow should fail but not crash") {
156+
TestActionWithProps
157+
.tester(withState: true)
158+
.send(action: .readOptionalProp)
159+
.assert(state: true)
160+
}
161+
}
131162
}
132163

133164
// MARK: -
134165

135166
private enum TestActionWithProps: WorkflowAction {
136167
typealias WorkflowType = TestWorkflow
137168

138-
case readProps
169+
case readProp
170+
case readOptionalProp
139171
case dontReadProps
140172

141173
func apply(
@@ -146,9 +178,13 @@ private enum TestActionWithProps: WorkflowAction {
146178
case .dontReadProps:
147179
return .value("did not read props")
148180

149-
case .readProps:
181+
case .readProp:
150182
let prop = context[workflowValue: \.prop]
151-
return .value("read props: \(prop)")
183+
return .value("read prop: \(prop)")
184+
185+
case .readOptionalProp:
186+
let optionalProp = context[workflowValue: \.optionalProp]
187+
return .value("read optional prop: \(optionalProp?.description ?? "<nil>")")
152188
}
153189
}
154190
}
@@ -179,6 +215,7 @@ private struct TestWorkflow: Workflow {
179215
}
180216

181217
var prop = 0
218+
var optionalProp: Int? = 42
182219

183220
func makeInitialState() -> Bool {
184221
true

0 commit comments

Comments
 (0)