diff --git a/Package.swift b/Package.swift index 67d698b22..f33f4db4c 100644 --- a/Package.swift +++ b/Package.swift @@ -65,6 +65,7 @@ let package = Package( .package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.4.0"), .package(url: "https://github.com/pointfreeco/swift-perception", from: "1.5.0"), .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.2.1"), + .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.0.0"), ], targets: [ // MARK: Workflow diff --git a/WorkflowCombine/Testing/WorkerTesting.swift b/WorkflowCombine/Testing/WorkerTesting.swift index 700f39e94..5a94aa1d7 100644 --- a/WorkflowCombine/Testing/WorkerTesting.swift +++ b/WorkflowCombine/Testing/WorkerTesting.swift @@ -15,6 +15,7 @@ */ #if DEBUG +import IssueReporting import Workflow import WorkflowTesting import XCTest @@ -62,9 +63,9 @@ extension RenderTester { guard !workflow.worker.isEquivalent(to: expectedWorker) else { return } - XCTFail( + reportIssue( "Workers of type \(ExpectedWorkerType.self) not equivalent. Expected: \(expectedWorker). Got: \(workflow.worker)", - file: file, + filePath: file, line: line ) } diff --git a/WorkflowConcurrency/Testing/WorkerTesting.swift b/WorkflowConcurrency/Testing/WorkerTesting.swift index d61627df3..650dc96b2 100644 --- a/WorkflowConcurrency/Testing/WorkerTesting.swift +++ b/WorkflowConcurrency/Testing/WorkerTesting.swift @@ -15,6 +15,7 @@ */ #if DEBUG +import IssueReporting import Workflow import WorkflowTesting import XCTest @@ -62,9 +63,9 @@ extension RenderTester { guard !workflow.worker.isEquivalent(to: worker) else { return } - XCTFail( + reportIssue( "Workers of type \(ExpectedWorkerType.self) not equivalent. Expected: \(worker). Got: \(workflow.worker)", - file: file, + filePath: file, line: line ) } diff --git a/WorkflowReactiveSwift/Testing/WorkerTesting.swift b/WorkflowReactiveSwift/Testing/WorkerTesting.swift index c91977914..a447324aa 100644 --- a/WorkflowReactiveSwift/Testing/WorkerTesting.swift +++ b/WorkflowReactiveSwift/Testing/WorkerTesting.swift @@ -15,6 +15,7 @@ */ #if DEBUG +import IssueReporting import Workflow import WorkflowTesting import XCTest @@ -62,9 +63,9 @@ extension RenderTester { guard !workflow.worker.isEquivalent(to: expectedWorker) else { return } - XCTFail( + reportIssue( "Workers of type \(ExpectedWorkerType.self) not equivalent. Expected: \(expectedWorker). Got: \(workflow.worker)", - file: file, + filePath: file, line: line ) } diff --git a/WorkflowRxSwift/Testing/WorkerTesting.swift b/WorkflowRxSwift/Testing/WorkerTesting.swift index ecbf04a22..e2364751d 100644 --- a/WorkflowRxSwift/Testing/WorkerTesting.swift +++ b/WorkflowRxSwift/Testing/WorkerTesting.swift @@ -15,6 +15,7 @@ */ #if DEBUG +import IssueReporting import Workflow import WorkflowTesting import XCTest @@ -62,9 +63,9 @@ extension RenderTester { guard !workflow.worker.isEquivalent(to: expectedWorker) else { return } - XCTFail( + reportIssue( "Workers of type \(ExpectedWorkerType.self) not equivalent. Expected: \(expectedWorker). Got: \(workflow.worker)", - file: file, + filePath: file, line: line ) } diff --git a/WorkflowTesting/Sources/Internal/AppliedAction.swift b/WorkflowTesting/Sources/Internal/AppliedAction.swift index 950464cbd..650d38695 100644 --- a/WorkflowTesting/Sources/Internal/AppliedAction.swift +++ b/WorkflowTesting/Sources/Internal/AppliedAction.swift @@ -14,6 +14,7 @@ * limitations under the License. */ +import IssueReporting import Workflow import XCTest @@ -26,7 +27,7 @@ struct AppliedAction { func assert(type: ActionType.Type = ActionType.self, file: StaticString, line: UInt, assertions: (ActionType) throws -> Void) rethrows where ActionType.WorkflowType == WorkflowType { guard let action = erasedAction as? ActionType else { - XCTFail("Expected action of type \(ActionType.self), got \(erasedAction)", file: file, line: line) + reportIssue("Expected action of type \(ActionType.self), got \(erasedAction)", filePath: file, line: line) return } try assertions(action) diff --git a/WorkflowTesting/Sources/Internal/RenderTester+TestContext.swift b/WorkflowTesting/Sources/Internal/RenderTester+TestContext.swift index 0bb2583d0..2893ebba8 100644 --- a/WorkflowTesting/Sources/Internal/RenderTester+TestContext.swift +++ b/WorkflowTesting/Sources/Internal/RenderTester+TestContext.swift @@ -16,7 +16,9 @@ #if DEBUG +import IssueReporting import XCTest + @testable import Workflow extension RenderTester { @@ -65,7 +67,7 @@ extension RenderTester { """ } let failureMessage = "Attempted to render unexpected Workflow of type \(Child.self) with key \"\(key)\". \(diagnosticMessage)" - XCTFail(failureMessage, file: file, line: line) + reportIssue(failureMessage, filePath: file, line: line) // We can “recover” from missing Void-rendering workflows since there’s only one possible value to return if Child.Rendering.self == Void.self { @@ -76,7 +78,7 @@ extension RenderTester { } let (inserted, _) = usedWorkflowKeys.insert(WorkflowKey(type: ObjectIdentifier(Child.self), key: key)) if !inserted { - XCTFail("Multiple Workflows of type \(Child.self) with key \"\(key)\" used in the same render call. Use a unique key to render multiple Workflows of the same type.", file: file, line: line) + reportIssue("Multiple Workflows of type \(Child.self) with key \"\(key)\" used in the same render call. Use a unique key to render multiple Workflows of the same type.", filePath: file, line: line) } expectedWorkflows.removeAll(where: { $0 === expectedWorkflow }) @@ -96,7 +98,7 @@ extension RenderTester { func runSideEffect(key: AnyHashable, action: (Lifetime) -> Void) { guard let sideEffect = expectedSideEffects.removeValue(forKey: key) else { - XCTFail("Unexpected side-effect with key \"\(key)\"", file: file, line: line) + reportIssue("Unexpected side-effect with key \"\(key)\"", filePath: file, line: line) return } @@ -106,21 +108,25 @@ extension RenderTester { /// Validate the expectations were fulfilled, or fail if not. func assertNoLeftOverExpectations() { for expectedWorkflow in expectedWorkflows { - XCTFail("Expected child workflow of type: \(expectedWorkflow.workflowType), key: \"\(expectedWorkflow.key)\"", file: file, line: expectedWorkflow.line) + reportIssue("Expected child workflow of type: \(expectedWorkflow.workflowType), key: \"\(expectedWorkflow.key)\"", filePath: file, line: expectedWorkflow.line) } for (key, expectedSideEffect) in expectedSideEffects { - XCTFail("Expected side-effect with key: \"\(key)\"", file: expectedSideEffect.file, line: expectedSideEffect.line) + reportIssue("Expected side-effect with key: \"\(key)\"", filePath: expectedSideEffect.file, line: expectedSideEffect.line) } } private func apply(action: ActionType) where ActionType.WorkflowType == WorkflowType { - XCTAssertNil(appliedAction, "Received multiple actions in a single render test", file: file, line: line) + if appliedAction != nil { + reportIssue("Received multiple actions in a single render test", filePath: file, line: line) + } appliedAction = AppliedAction(action) let output = action.apply(toState: &state) if let output { - XCTAssertNil(producedOutput, "Received multiple outputs in a single render test", file: file, line: line) + if producedOutput != nil { + reportIssue("Received multiple outputs in a single render test", filePath: file, line: line) + } producedOutput = output } } diff --git a/WorkflowTesting/Sources/RenderTesterResult.swift b/WorkflowTesting/Sources/RenderTesterResult.swift index 8df8e39e4..e1cfc3a32 100644 --- a/WorkflowTesting/Sources/RenderTesterResult.swift +++ b/WorkflowTesting/Sources/RenderTesterResult.swift @@ -15,6 +15,7 @@ */ import CustomDump +import IssueReporting import Workflow import XCTest @@ -56,7 +57,7 @@ public struct RenderTesterResult { line: UInt = #line ) -> RenderTesterResult { if let appliedAction { - XCTFail("Expected no action, but got \(appliedAction.erasedAction).", file: file, line: line) + reportIssue("Expected no action, but got \(appliedAction.erasedAction).", filePath: file, line: line) } return self } @@ -70,7 +71,7 @@ public struct RenderTesterResult { assertions: (ActionType) throws -> Void ) rethrows -> RenderTesterResult where ActionType.WorkflowType == WorkflowType { guard let appliedAction else { - XCTFail("No action was produced", file: file, line: line) + reportIssue("No action was produced", filePath: file, line: line) return self } try appliedAction.assert(file: file, line: line, assertions: assertions) @@ -106,7 +107,7 @@ public struct RenderTesterResult { line: UInt = #line ) -> RenderTesterResult { if let output { - XCTFail("Expected no output, but got \(output).", file: file, line: line) + reportIssue("Expected no output, but got \(output).", filePath: file, line: line) } return self } @@ -119,7 +120,7 @@ public struct RenderTesterResult { assertions: (WorkflowType.Output) throws -> Void ) rethrows -> RenderTesterResult { guard let output else { - XCTFail("No output was produced", file: file, line: line) + reportIssue("No output was produced", filePath: file, line: line) return self } try assertions(output) diff --git a/WorkflowTesting/Sources/WorkflowActionTester.swift b/WorkflowTesting/Sources/WorkflowActionTester.swift index 7865b5e6c..8017cbd8c 100644 --- a/WorkflowTesting/Sources/WorkflowActionTester.swift +++ b/WorkflowTesting/Sources/WorkflowActionTester.swift @@ -14,6 +14,8 @@ * limitations under the License. */ +import CustomDump +import IssueReporting import Workflow import XCTest @@ -91,7 +93,7 @@ public struct WorkflowActionTester where A line: UInt = #line ) -> WorkflowActionTester { if let output { - XCTFail("Expected no output, but got \(output).", file: file, line: line) + reportIssue("Expected no output, but got \(output).", filePath: file, line: line) } return self } @@ -109,7 +111,7 @@ public struct WorkflowActionTester where A _ assertions: (WorkflowType.Output) throws -> Void ) rethrows -> WorkflowActionTester { guard let output else { - XCTFail("No output was produced", file: file, line: line) + reportIssue("No output was produced", filePath: file, line: line) return self } try assertions(output) @@ -143,7 +145,7 @@ extension WorkflowActionTester where WorkflowType.State: Equatable { @discardableResult public func assert(state expectedState: WorkflowType.State, file: StaticString = #file, line: UInt = #line) -> WorkflowActionTester { verifyState { actualState in - XCTAssertEqual(actualState, expectedState, file: file, line: line) + expectNoDifference(actualState, expectedState, filePath: file, line: line) } } } @@ -157,7 +159,7 @@ extension WorkflowActionTester where WorkflowType.Output: Equatable { @discardableResult public func assert(output expectedOutput: WorkflowType.Output, file: StaticString = #file, line: UInt = #line) -> WorkflowActionTester { verifyOutput { actualOutput in - XCTAssertEqual(actualOutput, expectedOutput, file: file, line: line) + expectNoDifference(actualOutput, expectedOutput, filePath: file, line: line) } } } diff --git a/WorkflowTesting/Tests/TestingFrameworkCompatibilityTests.swift b/WorkflowTesting/Tests/TestingFrameworkCompatibilityTests.swift new file mode 100644 index 000000000..f5d9206b9 --- /dev/null +++ b/WorkflowTesting/Tests/TestingFrameworkCompatibilityTests.swift @@ -0,0 +1,65 @@ +/* + * Copyright Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Testing +import Workflow +import XCTest + +@testable import WorkflowTesting + +struct SwiftTestingCompatibilityTests { + @Test + func testInternalFailureRecordsExpectationFailure_swiftTesting() { + withKnownIssue { + TestAction + .tester(withState: false) + .send(action: .change(true)) + .assertNoOutput() // should fail the test + } + } +} + +final class XCTestCompatibilityTests: XCTestCase { + func testInternalFailureRecordsExpectationFailure_xctest() { + XCTExpectFailure { + _ = TestAction + .tester(withState: false) + .send(action: .change(true)) + .assertNoOutput() // should fail the test + } + } +} + +private enum TestAction: WorkflowAction { + typealias WorkflowType = TestWorkflow + + case change(Bool) + + func apply(toState state: inout Bool) -> TestWorkflow.Output? { + if case .change(let newState) = self { + state = newState + } + return 42 + } +} + +private struct TestWorkflow: Workflow { + typealias Rendering = Void + typealias Output = Int + + func makeInitialState() -> Bool { true } + func render(state: Bool, context: RenderContext) {} +}