Chapters

Hide chapters

iOS Test-Driven Development by Tutorials

Second Edition · iOS 15 · Swift 5.5 · Xcode 13

5. Test Expectations
Written by Michael Katz

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In the previous chapters you built out the app’s state based upon what the user can do with the Start button. The main part of the app relies on responding to changes as the user moves around and records steps. These actions create events outside the program’s control. XCTestExpectation is the tool for testing things that happen outside the direct flow.

In this chapter you’ll learn:

  • General test expectations
  • Notification expectations

Use this chapter’s starter project instead of continuing on from the previous’ final, as it has some additions to help you out.

Using an expectation

XCTest expectations have two parts: the expectation and a waiter. An expectation is an object that you can later fulfill. The wait method of XCTestCase tells the test execution to wait until the expectation is fulfilled or a specified amount of time passes.

In the last chapter you built out the app states corresponding to direct user action: in progress, paused, and not started. In this chapter you’ll add support for caught and completed.

These state transitions occur in response to asynchronous events outside the user’s control.

Nessie overtakes user User reaches goal Caught Completed Not Started In Progress Paused start() pause() resume() Start restart()

The red-shaded states have already been built. You’ll be adding the grey states.

Writing an asynchronous test

In order to react to an asynchronous event, the code needs a way to listen for a change. This is commonly done through a closure, a delegate method, or by observing a notification.

Testing for true asynchronicity

The last test checks that the callback is called in direct response to an update on the sut. Next, you’ll tackle a more indirect usage via updates to the view controller. Open StepCountControllerTests.swift at the end of // MARK: - Terminal States add the following two tests:

Waiting for notifications

In the next phase of app building, you’ll add a feature to visually notify the users when an event happens, such as meeting a milestone goal or when Nessie catches up.

Building the alert center

One important feature for an activity app or game is to update the user when important events happen. In FitNess these updates are managed by an AlertCenter. When something interesting happens, the code will post Alerts to the AlertCenter. The alert center is responsible for managing a stack of messages to display to the user.

Ivgikoyk Cvuznaf Pura Xuruy Axojq Cehraz Odafp Geef Bilhpenxom gtozz uhdalir weusxem 03% in soic gidssur aqozz kuejw

Waiting for multiple events

Next, try testing if posting two alerts sends two notifications. Add the following to the end of AlertCenterTests:

Expecting something not to happen

Good test suites not only test when things happen according to plan, but also check that certain side effects do not occur. One of things the app should not do is spam the user with alerts. Therefore, if a specific alert is posted twice, it should only generate one notification.

Showing the alert to a user

In the app’s architecture, the RootViewController is responsible for showing alerts to the user via its alertContainer view.

Continuous refactoring

When you only run testWhenLoaded_noAlertsAreShown(), it will pass. If you run all the tests in RootViewControllerTests, then testWhenLoaded_noAlertsAreShown() may fail.

Error: This image is missing a width attribute

Please provide one in the form of ![width=50%](images/alert_showing.png)

Getting specific about notifications

To make sure the UI is updated effectively, it will be useful to add additional information to the alert notification beyond the name.

Driving alerts from the data model

In order to drive engagement and give the user a sense of fulfillment as they near their goal, it’s important to present messages to the user as they reach certain milestones.

Testing for multiple expectations

Your new milestone notification tests all seem pretty similar. This is an indicator that you should refactor them to reduce repeated code.

Refining Requirements

The previous set of unit tests have one flaw when it comes to validating the app. They test a snapshot of the app’s state and do not consider that the app is dynamic.

Using other types of expectations

The bulk of the time you’re testing asynchronous processes, you’ll use a regular XCTestExpectation. XCTNSNotificationExpectation covers most other needs. For specific uses, there are two other stock expectations: XCTKVOExpectation and XCTNSPredicateExpectation.

Challenge

This tutorial only scratched the surface of testing asynchronous functions. Here are some things to add to the app with test coverage:

Key points

  • Use XCTestExpectation and its subclasses to make tests wait for asynchronous process completion.
  • Test expectations help test properties of the asynchronicity, like order and number of occurrences, but XCTAssert functions should still be used to test state.

Where to go from here?

So much app code is asynchronous by nature—disk and network access, UI events, system callbacks, and so on. It’s important to understand how to test that code, and this chapter gives you a good start. Many popular 3rd party testing frameworks also have functions that make writing these types of tests easier. For example Quick+Nimble allows you to write an assert, expectation and wait in one line:

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now