Skip to content
tezvyn:

Testing async code and completion handlers in XCTest

Source: interviewMediumHow cards are made

Summary

knowledge of waiting for asynchronous work in tests.

Key points

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

What's really being asked

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.

The full answer

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.

The mistakes people make

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.

What usually comes next

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?

A 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.

Interview question

What happens if you assert on a completion handler's result without using an expectation or async await?

  • a.The test method may finish before the callback runs, passing incorrectlyCorrect
  • b.The compiler rejects the test for missing a timeout
  • c.XCTest automatically retries the test until the callback fires
  • d.The assertions run twice, once before and once after the callback
Why?

Without waiting, the test method returns before the async callback executes, so assertions inside it never run and the test passes falsely. XCTest does not auto-retry or auto-wait, and there is no double execution.

Just read this? Test yourself on what you have been reading.

Read the original → developer.apple.com

Put your scrolling time to good use

Learn one idea, try a quiz and save useful cards for revision. Tezvyn makes it easy to learn and stay current in your tech field, a few minutes at a time.

The iPhone app is on the way

We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.

Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.

Get it on Google PlayiPhone app coming soon

We are hiring for this. Open roles that interview on ios — each one lists the topics its interview covers.

See open roles