WWDC24 SALE: Save 50% on all my Swift books and bundles! >>

How to test asynchronous functions using expectation()

Swift version: 5.10

Paul Hudson    @twostraws   

XCTestCase has the built-in ability to work with asynchronous code using a system of expectations. First, you create one of more instances of XCTestExpectation using the expectation() method, then you run your asynchronous code, and finally you call waitForExpectations() so the test doesn’t end prematurely.

When your asynchronous code completes you call fulfill() on it to mark it as complete, and you can then call some variant of XCTAssert() to check whether the test succeeded or failed.

As an example, I have a FeedParser struct that loads stories from disk and parses them ready for display. It takes a few milliseconds to run, so to avoid freezing my app it has an asynchronous method called loadStories() that calls a completion handler when the stories are ready to be used. Using XCTestCase expectations I would write a test like this:

func testStoryLoading() throws {
    let parser = FeedParser()

    // create the expectation
    let exp = expectation(description: "Loading stories")

    // call my asynchronous method
    parser.loadStories {
        // when it finishes, mark my expectation as being fulfilled

    // wait three seconds for all outstanding expectations to be fulfilled
    waitForExpectations(timeout: 3)

    // our expectation has been fulfilled, so we can check the result is correct
    XCTAssertEqual(parser.stories.count, 20, "We should have loaded exactly 20 stories.")

If my asynchronous code does not complete in the allotted time of three seconds, the test is considered an immediate failure.

XCTestExpectation has two properties you might want to explore further. The first is isInverted: if you set that to true then the test will be considered a failure if fulfill() gets called before the time out, so for example you might want the AI in your game to wait at least two seconds before making its move so the player can see it’s definitely thinking.

The second is expectedFulfillmentCount: if you set this to 5 for example, it means fulfill() must be called five times before the time out is reached, which allows you to implement more advanced testing logic.

Save 50% in my WWDC sale.

SAVE 50% To celebrate WWDC24, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Available from iOS

Similar solutions…

About the Swift Knowledge Base

This is part of the Swift Knowledge Base, a free, searchable collection of solutions for common iOS questions.

Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 5.0/5

Unknown user

You are not logged in

Log in or create account

Link copied to your pasteboard.