Skip to content

Commit

Permalink
Make WorkflowRenderTester and RenderTesterResult structs
Browse files Browse the repository at this point in the history
This makes it a little more clear when mutations are happening,
and makes it less weird that we mutate a shared thing while
also passing it along (e.g. it’s now clear that the _only_
way to correctly use the render tester is to use the tester
that results from each call as you go)
  • Loading branch information
bencochran committed Jun 29, 2020
1 parent f1e1163 commit d643163
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 42 deletions.
32 changes: 16 additions & 16 deletions WorkflowTesting/Sources/DeprecatedRenderExpectations.swift
Expand Up @@ -97,7 +97,7 @@
@available(*, deprecated, message: "See `RenderTester` documentation for new style.")
public struct ExpectedWorker {
fileprivate class AnyStorage {
func expect<WorkflowType: Workflow>(in tester: RenderTester<WorkflowType>) {
func expect<WorkflowType: Workflow>(in tester: inout RenderTester<WorkflowType>) {
fatalError()
}
}
Expand All @@ -111,8 +111,8 @@
self.output = output
}

override func expect<WorkflowType: Workflow>(in tester: RenderTester<WorkflowType>) {
_ = tester.expect(worker: worker, producingOutput: output)
override func expect<WorkflowType: Workflow>(in tester: inout RenderTester<WorkflowType>) {
tester = tester.expect(worker: worker, producingOutput: output)
}
}

Expand All @@ -124,8 +124,8 @@
self.storage = Storage(worker: worker, output: output)
}

func expect<ParentWorkflowType: Workflow>(in tester: RenderTester<ParentWorkflowType>) {
storage.expect(in: tester)
func expect<ParentWorkflowType: Workflow>(in tester: inout RenderTester<ParentWorkflowType>) {
storage.expect(in: &tester)
}
}

Expand All @@ -138,8 +138,8 @@
self.key = key
}

func expect(in tester: RenderTester<WorkflowType>) {
_ = tester.expectSideEffect(key: key)
func expect(in tester: inout RenderTester<WorkflowType>) {
tester = tester.expectSideEffect(key: key)
}
}

Expand All @@ -151,8 +151,8 @@
super.init(key: key)
}

override func expect(in tester: RenderTester<WorkflowType>) {
_ = tester.expectSideEffect(key: key, producingAction: action)
override func expect(in tester: inout RenderTester<WorkflowType>) {
tester = tester.expectSideEffect(key: key, producingAction: action)
}
}

Expand All @@ -166,15 +166,15 @@
self.storage = StorageWithAction(key: key, action: action)
}

func expect(in tester: RenderTester<WorkflowType>) {
storage.expect(in: tester)
func expect(in tester: inout RenderTester<WorkflowType>) {
storage.expect(in: &tester)
}
}

@available(*, deprecated, message: "See `RenderTester` documentation for new style.")
public struct ExpectedWorkflow {
fileprivate class AnyStorage {
func expect<ParentWorkflowType: Workflow>(in tester: RenderTester<ParentWorkflowType>) {
func expect<ParentWorkflowType: Workflow>(in tester: inout RenderTester<ParentWorkflowType>) {
fatalError()
}
}
Expand All @@ -190,8 +190,8 @@
self.output = output
}

override func expect<WorkflowType: Workflow>(in tester: RenderTester<WorkflowType>) {
_ = tester.expectWorkflow(type: ExpectedWorkflowType.self, key: key, producingRendering: rendering, producingOutput: output)
override func expect<WorkflowType: Workflow>(in tester: inout RenderTester<WorkflowType>) {
tester = tester.expectWorkflow(type: ExpectedWorkflowType.self, key: key, producingRendering: rendering, producingOutput: output)
}
}

Expand All @@ -201,8 +201,8 @@
self.storage = Storage<WorkflowType>(key: key, rendering: rendering, output: output)
}

func expect<ParentWorkflowType: Workflow>(in tester: RenderTester<ParentWorkflowType>) {
storage.expect(in: tester)
func expect<ParentWorkflowType: Workflow>(in tester: inout RenderTester<ParentWorkflowType>) {
storage.expect(in: &tester)
}
}

Expand Down
2 changes: 1 addition & 1 deletion WorkflowTesting/Sources/RenderTesterResult.swift
Expand Up @@ -19,7 +19,7 @@ import XCTest

/// The result of a `RenderTester` rendering. Used to verify state, output, and actions that were produced as a result of
/// actions performed during the render (such as child workflow output being produced).
public final class RenderTesterResult<WorkflowType: Workflow> {
public struct RenderTesterResult<WorkflowType: Workflow> {
let state: WorkflowType.State
let appliedAction: AppliedAction<WorkflowType>?
let output: WorkflowType.Output?
Expand Down
12 changes: 7 additions & 5 deletions WorkflowTesting/Sources/WorkflowRenderTester+Deprecated.swift
Expand Up @@ -22,19 +22,21 @@
@available(*, deprecated, message: "See `RenderTester` documentation for new style.")
@discardableResult
public func render(file: StaticString = #file, line: UInt = #line, with expectations: RenderExpectations<WorkflowType>, assertions: (WorkflowType.Rendering) -> Void) -> RenderTester<WorkflowType> {
var tester = self

for expectedWorkflow in expectations.expectedWorkflows {
expectedWorkflow.expect(in: self)
expectedWorkflow.expect(in: &tester)
}

for expectedWorker in expectations.expectedWorkers {
expectedWorker.expect(in: self)
expectedWorker.expect(in: &tester)
}

for (_, expectedSideEffect) in expectations.expectedSideEffects {
expectedSideEffect.expect(in: self)
expectedSideEffect.expect(in: &tester)
}

let result = render(assertions: assertions)
let result = tester.render(assertions: assertions)

if let expectedState = expectations.expectedState {
expectedState.verify(in: result, file: file, line: line)
Expand All @@ -45,7 +47,7 @@
}

return RenderTester(
workflow: workflow,
workflow: tester.workflow,
state: result.state
)
}
Expand Down
84 changes: 65 additions & 19 deletions WorkflowTesting/Sources/WorkflowRenderTester.swift
Expand Up @@ -118,17 +118,26 @@
/// )
/// .render { _ in }
/// ```
public final class RenderTester<WorkflowType: Workflow> {
public struct RenderTester<WorkflowType: Workflow> {
let workflow: WorkflowType
let state: WorkflowType.State

private var expectedWorkflows: [AnyExpectedWorkflow] = []
private var expectedWorkers: [AnyExpectedWorker] = []
private var expectedSideEffects: [AnyHashable: ExpectedSideEffect<WorkflowType>] = [:]
private let expectedWorkflows: [AnyExpectedWorkflow]
private let expectedWorkers: [AnyExpectedWorker]
private let expectedSideEffects: [AnyHashable: ExpectedSideEffect<WorkflowType>]

init(workflow: WorkflowType, state: WorkflowType.State) {
init(
workflow: WorkflowType,
state: WorkflowType.State,
expectedWorkflows: [AnyExpectedWorkflow] = [],
expectedWorkers: [AnyExpectedWorker] = [],
expectedSideEffects: [AnyHashable: ExpectedSideEffect<WorkflowType>] = [:]
) {
self.workflow = workflow
self.state = state
self.expectedWorkflows = expectedWorkflows
self.expectedWorkers = expectedWorkers
self.expectedSideEffects = expectedSideEffects
}

/// Expect the given workflow type in the next rendering.
Expand All @@ -146,15 +155,19 @@
producingOutput output: ExpectedWorkflowType.Output? = nil,
assertions: (ExpectedWorkflowType) -> Void = { _ in }
) -> RenderTester<WorkflowType> {
expectedWorkflows
.append(
return RenderTester(
workflow: workflow,
state: state,
expectedWorkflows: expectedWorkflows.appending(
ExpectedWorkflow<ExpectedWorkflowType>(
key: key,
rendering: rendering,
output: output
)
)
return self
),
expectedWorkers: expectedWorkers,
expectedSideEffects: expectedSideEffects
)
}

/// Expect the given worker. It will be checked for `isEquivalent(to:)` with the requested worker.
Expand All @@ -166,23 +179,34 @@
worker: ExpectedWorkerType,
producingOutput output: ExpectedWorkerType.Output? = nil
) -> RenderTester<WorkflowType> {
expectedWorkers
.append(
return RenderTester(
workflow: workflow,
state: state,
expectedWorkflows: expectedWorkflows,
expectedWorkers: expectedWorkers.appending(
ExpectedWorker(
worker: worker,
output: output
)
)
return self
),
expectedSideEffects: expectedSideEffects
)
}

/// Expect a side-effect for the given key.
///
/// - Parameter key: The key to expect.
public func expectSideEffect(key: AnyHashable) -> RenderTester<WorkflowType> {
// TODO: Assert not already expecting
expectedSideEffects[key] = ExpectedSideEffect(key: key)
return self
return RenderTester(
workflow: workflow,
state: state,
expectedWorkflows: expectedWorkflows,
expectedWorkers: expectedWorkers,
expectedSideEffects: expectedSideEffects.setting(
key: key,
value: ExpectedSideEffect(key: key)
)
)
}

/// Expect a side-effect for the given key, and produce the given action when it is requested.
Expand All @@ -194,11 +218,19 @@
key: AnyHashable,
producingAction action: ActionType
) -> RenderTester<WorkflowType> where ActionType: WorkflowAction, ActionType.WorkflowType == WorkflowType {
expectedSideEffects[key] = ExpectedSideEffectWithAction(key: key, action: action)
return self
return RenderTester(
workflow: workflow,
state: state,
expectedWorkflows: expectedWorkflows,
expectedWorkers: expectedWorkers,
expectedSideEffects: expectedSideEffects.setting(
key: key,
value: ExpectedSideEffectWithAction(key: key, action: action)
)
)
}

/// Render the workflow under test. At this point, you should have set up all expecatations.
/// Render the workflow under test. At this point, you should have set up all expectations.
///
/// The given `assertions` closure will be called with the produced rendering, allowing you to assert its properties or
/// perform actions on it (such as closures that are wired up to a `Sink` inside the workflow.
Expand Down Expand Up @@ -234,4 +266,18 @@
}
}

extension Collection {
fileprivate func appending(_ element: Element) -> [Element] {
return self + [element]
}
}

extension Dictionary {
fileprivate func setting(key: Key, value: Value) -> [Key: Value] {
var newDictionary = self
newDictionary[key] = value
return newDictionary
}
}

#endif
40 changes: 39 additions & 1 deletion WorkflowTesting/Tests/WorkflowRenderTesterDeprecatedTests.swift
Expand Up @@ -208,7 +208,7 @@ final class WorkflowRenderTesterDeprecatedTests: XCTestCase {
}
}

func test_implict_expectations() {
func test_impplict_expectations() {
TestWorkflow(initialText: "hello")
.renderTester()
.render(
Expand All @@ -226,6 +226,44 @@ final class WorkflowRenderTesterDeprecatedTests: XCTestCase {
}
)
}

func test_multiple_renders() {
ParentWorkflow(initialText: "hello")
.renderTester()
.render(
expectedState: ExpectedState(
state: ParentWorkflow.State(text: "Failed")
),
expectedWorkflows: [
ExpectedWorkflow(
type: ChildWorkflow.self,
rendering: "olleh",
output: .failure
),
],
assertions: { rendering in
XCTAssertEqual("olleh", rendering)
}
)
.assert { state in
XCTAssertEqual("Failed", state.text)
}
.render(
expectedState: ExpectedState(
state: ParentWorkflow.State(text: "deliaF")
),
expectedWorkflows: [
ExpectedWorkflow(
type: ChildWorkflow.self,
rendering: "olleh",
output: .success
),
],
assertions: { rendering in
XCTAssertEqual("olleh", rendering)
}
)
}
}

private struct TestWorkflow: Workflow {
Expand Down

0 comments on commit d643163

Please sign in to comment.