diff --git a/Package.swift b/Package.swift index 5bd0e38d5..48edd155c 100644 --- a/Package.swift +++ b/Package.swift @@ -66,6 +66,11 @@ let package = Package( dependencies: ["Workflow"], path: "Workflow/Tests" ), + .testTarget( + name: "WorkflowTestingTests", + dependencies: ["WorkflowTesting"], + path: "WorkflowTesting/Tests" + ), .target( name: "WorkflowUI", dependencies: ["Workflow"], @@ -116,6 +121,11 @@ let package = Package( dependencies: ["WorkflowReactiveSwift", "WorkflowTesting"], path: "WorkflowReactiveSwift/Testing" ), + .testTarget( + name: "WorkflowReactiveSwiftTestingTests", + dependencies: ["WorkflowReactiveSwiftTesting"], + path: "WorkflowReactiveSwift/TestingTests" + ), .target( name: "WorkflowCombineTesting", dependencies: ["WorkflowCombine", "WorkflowTesting"], diff --git a/WorkflowReactiveSwift/Testing/SignalProducerWorkflowTesting.swift b/WorkflowReactiveSwift/Testing/SignalProducerWorkflowTesting.swift index 3f80ca4cb..2123687a5 100644 --- a/WorkflowReactiveSwift/Testing/SignalProducerWorkflowTesting.swift +++ b/WorkflowReactiveSwift/Testing/SignalProducerWorkflowTesting.swift @@ -25,6 +25,8 @@ /// /// `SignalProducerWorkflow` is used to subscribe to `SignalProducer`s and `Signal`s. /// + /// ⚠️ N.B. If you are testing a case in which multiple `SignalProducerWorkflow`s are expected, **only one of them** may have a non-nil `producingOutput` parameter. + /// /// - Parameters: /// - producingOutput: An output that should be returned when this worker is requested, if any. /// - key: Key to expect this `Workflow` to be rendered with. @@ -41,5 +43,23 @@ assertions: { _ in } ) } + + /// Expect a `SignalProducer` with the specified `outputType` that produces no `Output`. + /// + /// - Parameters: + /// - outputType: The `OutputType` of the expected `SignalProducerWorkflow`. + /// - key: Key to expect this `Workflow` to be rendered with. + public func expectSignalProducer( + outputType: OutputType.Type, + key: String = "", + file: StaticString = #file, line: UInt = #line + ) -> RenderTester { + expectWorkflow( + type: SignalProducerWorkflow.self, + key: key, + producingRendering: (), + assertions: { _ in } + ) + } } #endif diff --git a/WorkflowReactiveSwift/TestingTests/SignalProducerTests.swift b/WorkflowReactiveSwift/TestingTests/SignalProducerTests.swift index d33f7476f..ddea52cc7 100644 --- a/WorkflowReactiveSwift/TestingTests/SignalProducerTests.swift +++ b/WorkflowReactiveSwift/TestingTests/SignalProducerTests.swift @@ -21,21 +21,47 @@ import WorkflowReactiveSwiftTesting import XCTest class SignalProducerTests: XCTestCase { - func testSignalProducerWorkflow() { + func test_signalProducerWorkflow() { TestWorkflow() .renderTester() .expectSignalProducer(producingOutput: 1, key: "123") .render {} } - struct TestWorkflow: Workflow { + func test_multipleChildSignalProducerWorkflows() { + TestWorkflow(childKeys: ["123", "456"]) + .renderTester() + .expectSignalProducer( + // value is arbitrary, but needed to specify the output type + producingOutput: 42, + key: "123" + ) + .expectSignalProducer( + producingOutput: nil as Int?, + key: "456" + ) + .render {} + } + + func test_signalProducerWorkflow_noOutput() { + TestWorkflow() + .renderTester() + .expectSignalProducer(outputType: Int.self, key: "123") + .render {} + } + + private struct TestWorkflow: Workflow { typealias State = Void typealias Rendering = Void + var childKeys: [String] = ["123"] + func render(state: State, context: RenderContext) -> Rendering { - SignalProducer(value: 1) - .mapOutput { _ in AnyWorkflowAction.noAction } - .running(in: context, key: "123") + for key in childKeys { + SignalProducer(value: 1) + .mapOutput { _ in AnyWorkflowAction.noAction } + .running(in: context, key: key) + } } } } diff --git a/WorkflowTesting/Sources/Internal/RenderTester+TestContext.swift b/WorkflowTesting/Sources/Internal/RenderTester+TestContext.swift index cde14f299..bb4d33367 100644 --- a/WorkflowTesting/Sources/Internal/RenderTester+TestContext.swift +++ b/WorkflowTesting/Sources/Internal/RenderTester+TestContext.swift @@ -62,7 +62,9 @@ } else if sameKeyDifferentTypes.count > 1 { diagnosticMessage = "Found expectations for types \(sameKeyDifferentTypes) with key \"\(key)\"." } else { - diagnosticMessage = "" + diagnosticMessage = """ + If this child Workflow is expected, please add a call to `expectWorkflow(...)` with the appropriate parameters before invoking `render()`. + """ } XCTFail("Unexpected workflow of type \(Child.self) with key \"\(key)\". \(diagnosticMessage)", file: file, line: line) diff --git a/WorkflowTesting/Sources/WorkflowRenderTester.swift b/WorkflowTesting/Sources/WorkflowRenderTester.swift index 2d3199941..33e21fded 100644 --- a/WorkflowTesting/Sources/WorkflowRenderTester.swift +++ b/WorkflowTesting/Sources/WorkflowRenderTester.swift @@ -53,7 +53,11 @@ /// type: ChildWorkflow.self, /// key: "key", /// rendering: "rendering", - /// producingOutput: ChildWorkflow.Output.success + /// // ⚠️ N.B. Only one output per call to `render` may be produced, + /// // even if multiple child Workflows are expected in a call + /// // to `render`. This is an invariant enforced by `RenderTester` + /// // and the real runtime. + /// producingOutput: nil /// ) /// .render { rendering in /// XCTAssertEqual("expected text on rendering", rendering.text) diff --git a/WorkflowTesting/Tests/WorkflowRenderTesterFailureTests.swift b/WorkflowTesting/Tests/WorkflowRenderTesterFailureTests.swift index 63f2e2881..286d8ea72 100644 --- a/WorkflowTesting/Tests/WorkflowRenderTesterFailureTests.swift +++ b/WorkflowTesting/Tests/WorkflowRenderTesterFailureTests.swift @@ -127,7 +127,7 @@ final class WorkflowRenderTesterFailureTests: XCTestCase { let tester = TestWorkflow() .renderTester(initialState: .voidWorkflow) - expectingFailure(#"Unexpected workflow of type VoidWorkflow with key """#) { + expectingFailure(#"Unexpected workflow of type VoidWorkflow with key "". If this child Workflow is expected, please add a call to `expectWorkflow(...)` with the appropriate parameters before invoking `render()`."#) { tester.render { _ in } } }