Search code examples
swiftasync-awaitxctestexpectation

test swift async function timeout


How do you write a unit test that checks whether an async function doesn't timeout?

I'm trying with regular XCTestExpectation, but because await suspends everything, it can't wait for the expectation.

In the code below, I'm checking that loader.perform() doesn't take more than 1 second to execute.

func testLoaderSuccess() async throws {    
    let expectation = XCTestExpectation(description: "doesn't timeout")

    let result = try await loader.perform()

    XCTAssert(result.value == 42)

    wait(for: [expectation], timeout: 1) // execution never gets here

    expectation.fulfill()
}

Solution

  • It might be prudent to cancel the task if it times out:

    func testA() async throws {
        let expectation = XCTestExpectation(description: "timeout")
    
        let task = Task {
            let result = try await loader.perform()
            XCTAssertEqual(result, 42)
            expectation.fulfill()
        }
    
        await fulfillment(of: [expectation], timeout: 1)
        task.cancel()
    }
    

    If you do not, perform may continue to run even after testA finishes in the failure scenario.


    The other approach would be to use a task group:

    func testB() async throws {
        try await withThrowingTaskGroup(of: Void.self) { group in
            group.addTask {
                let result = try await self.loader.perform()
                XCTAssertEqual(result, 42)
            }
            group.addTask {
                try await Task.sleep(for: .seconds(1))
                XCTFail("Timed out")
            }
            let _ = try await group.next() // wait for the first one
            group.cancelAll()              // cancel the other one
        }
    }