wwt / SwiftCurrent

A library for managing complex workflows in Swift
https://wwt.github.io/SwiftCurrent/
Apache License 2.0
307 stars 19 forks source link

Refactored SwiftUI tests to use async/await APIs #171

Closed Tyler-Keith-Thompson closed 2 years ago

Tyler-Keith-Thompson commented 2 years ago

Linked Issue:

Checklist:


If Applicable:

Tyler-Keith-Thompson commented 2 years ago

I recognize this refactor can be hard to grok. Here's your key takeaway. I am turning this horrible pyramid of doom:

func testRemovedAfterProceeding_OnMiddleItemInAWorkflow() throws {
    struct FR1: View, FlowRepresentable, Inspectable {
        var _workflowPointer: AnyFlowRepresentable?
        var body: some View { Text("FR1 type") }
    }
    struct FR2: View, FlowRepresentable, Inspectable {
        var _workflowPointer: AnyFlowRepresentable?
        var body: some View { Text("FR2 type") }
    }
    struct FR3: View, FlowRepresentable, Inspectable {
        var _workflowPointer: AnyFlowRepresentable?
        var body: some View { Text("FR3 type") }
    }
    struct FR4: View, FlowRepresentable, Inspectable {
        var _workflowPointer: AnyFlowRepresentable?
        var body: some View { Text("FR4 type") }
    }
    let expectViewLoaded = ViewHosting.loadView(
        WorkflowLauncher(isLaunched: .constant(true)) {
            thenProceed(with: FR1.self) {
                thenProceed(with: FR2.self) {
                    thenProceed(with: FR3.self) {
                        thenProceed(with: FR4.self)
                    }
                }
                .persistence(.removedAfterProceeding)
            }
        }
    ).inspection.inspect { fr1 in
        XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow())
        try fr1.actualView().inspectWrapped { fr2 in
            XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow())
            try fr2.actualView().inspectWrapped { fr3 in
                XCTAssertNoThrow(try fr3.find(FR3.self).actualView().backUpInWorkflow())
                try fr1.actualView().inspect { fr1 in
                    XCTAssertNoThrow(try fr1.find(FR1.self).actualView().proceedInWorkflow())
                    try fr1.actualView().inspectWrapped { fr2 in
                        XCTAssertNoThrow(try fr2.find(FR2.self).actualView().proceedInWorkflow())
                        try fr2.actualView().inspectWrapped { fr3 in
                            XCTAssertNoThrow(try fr3.find(FR3.self).actualView().proceedInWorkflow())
                            try fr3.actualView().inspectWrapped { fr4 in
                                XCTAssertNoThrow(try fr4.find(FR4.self).actualView().proceedInWorkflow())
                            }
                        }
                    }
                }
            }
        }
    }

    wait(for: [expectViewLoaded], timeout: TestConstant.timeout)
}

into this much less horrible serial set of tests

func testRemovedAfterProceeding_OnMiddleItemInAWorkflow() async throws {
    struct FR1: View, FlowRepresentable, Inspectable {
        var _workflowPointer: AnyFlowRepresentable?
        var body: some View { Text("FR1 type") }
    }
    struct FR2: View, FlowRepresentable, Inspectable {
        var _workflowPointer: AnyFlowRepresentable?
        var body: some View { Text("FR2 type") }
    }
    struct FR3: View, FlowRepresentable, Inspectable {
        var _workflowPointer: AnyFlowRepresentable?
        var body: some View { Text("FR3 type") }
    }
    struct FR4: View, FlowRepresentable, Inspectable {
        var _workflowPointer: AnyFlowRepresentable?
        var body: some View { Text("FR4 type") }
    }
    let launcher = try await MainActor.run {
        WorkflowLauncher(isLaunched: .constant(true)) {
            thenProceed(with: FR1.self) {
                thenProceed(with: FR2.self) {
                    thenProceed(with: FR3.self) {
                        thenProceed(with: FR4.self)
                    }
                }
                .persistence(.removedAfterProceeding)
            }
        }
    }.hostAndInspect(with: \.inspection)

    try await launcher.find(FR1.self).proceedInWorkflow()
    try await launcher.find(FR2.self).proceedInWorkflow()
    try await launcher.find(FR3.self).backUpInWorkflow()

    try await launcher.find(FR1.self).proceedInWorkflow()
    try await launcher.find(FR2.self).proceedInWorkflow()
    try await launcher.find(FR3.self).proceedInWorkflow()
    try await launcher.find(FR4.self).proceedInWorkflow()
}
Tyler-Keith-Thompson commented 2 years ago

WARNING DANGER WILL ROBINSON: So this works, and the refactor is definitely nicer to read and work with. However, our pipeline is failing on timeouts. I've run this on really poor machines and on really performant machines. I do not believe it's timing out because there's a case. I believe it's timing out because the pipeline is not on Monterrey. So despite the new test niceties, we cannot merge this without our pipeline failing :(

codecov-commenter commented 2 years ago

Codecov Report

Merging #171 (c39a009) into main (edaedd0) will not change coverage. The diff coverage is n/a.

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #171   +/-   ##
=======================================
  Coverage   91.28%   91.28%           
=======================================
  Files          92       92           
  Lines        2375     2375           
=======================================
  Hits         2168     2168           
  Misses        207      207           

Continue to review full report at Codecov.

Legend - Click here to learn more Δ = absolute <relative> (impact), ø = not affected, ? = missing data Powered by Codecov. Last update 24dfab7...c39a009. Read the comment docs.