tezvyn:

Testing async code and completion handlers in XCTest

AI-drafted, machine-checkedSource: interviewintermediate
WHAT IT TESTS

knowledge of waiting for asynchronous work in tests.

OUTLINE

create an expectation, fulfill it inside the completion handler, call wait with a timeout, or use async test methods.

WHAT THIS TESTS The interviewer checks whether you understand that a test method returns immediately, so without explicit waiting the assertions inside an asynchronous callback never run before the test finishes and passes falsely. This is fundamental to writing reliable tests around networking, timers, and concurrency.

A GOOD ANSWER COVERS An XCTestExpectation is an object representing an event you expect to occur. You create one with expectation(description:), invoke the function under test, and inside its completion handler perform your assertions then call expectation.fulfill. After invoking, you call wait(for:timeout:) which blocks the test until the expectation is fulfilled or the timeout elapses, failing the test on timeout. With Swift Concurrency you can instead mark the test func async throws and await the call directly, removing the expectation boilerplate entirely.

COMMON WRONG ANSWERS Using Thread.sleep to wait a fixed duration, which is both flaky and slow. Asserting synchronously right after the call, before the callback fires. Forgetting the timeout, so a callback that never fires hangs CI indefinitely. Fulfilling an expectation more than once without setting assertForOverFulfill appropriately.

LIKELY FOLLOW-UPS How do you test that something does NOT happen? How do you wait for multiple expectations or enforce ordering with enforceOrder? How would you migrate a completion-handler test to async/await?

ONE CONCRETE EXAMPLE Testing fetchProfile(completion:): let exp = expectation(description: "profile loaded"); sut.fetchProfile { result in XCTAssertEqual(try? result.get().name, "Ada"); exp.fulfill() }; wait(for: [exp], timeout: 1.0). If the async variant exists, the test becomes func testFetch() async throws { let profile = try await sut.fetchProfile(); XCTAssertEqual(profile.name, "Ada") }, which is shorter and harder to get wrong.

Read the original → developer.apple.com

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.