NEW! Pre-order my latest book, Testing Swift! >>

How to test asynchronous functions using expectation()

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
        exp.fulfill()
    }

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

Available from iOS

Did this solution work for you? Please pass it on!

Other people are reading…

About the Swift Knowledge Base

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

Need to know Objective-C fast?

I wrote a book dedicated to teaching Objective-C to developers who already know Swift – it's the fastest way to get up to speed!

MASTER SWIFT NOW
Buy Testing Swift Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let me know!

Average rating: 5.0/5

Click here to visit the Hacking with Swift store >>