22.9 · XCTest lifecycle and expectations, Sendable doubles, and the main actor in tests

Details the first pass skipped: the exact order of XCTest’s lifecycle, how expectations fail, how to write test doubles that compile in Swift 6, and which thread a test runs on.

Mental model. A test is a small program with a lifecycle, a thread and an isolation domain. Most mystifying failures come from not knowing one of the three.

XCTest mechanics

Lifecycle order and how expectations fail.

  1. In what order do XCTest’s class and instance setup, teardown blocks and tearDown run, and what does continueAfterFailure change? 22.9.1 · L3
    For a test class, override class func setUp() runs once before the first test and class func tearDown() once after the last. For each test method an instance…
  2. How do XCTestExpectation tests fail: timeouts, inverted expectations, ordering, over-fulfilment and forgetting to wait? 22.9.2 · L5
    Each way an expectation goes wrong has its own message, and they are worth recognising. A timeout reports “Asynchronous wait failed: Exceeded timeout of 0.1…

Concurrency in tests

Sendable doubles and where a test runs.

  1. Why does a hand-written spy fail to compile in Swift 6 when a protocol is Sendable, and how do you write doubles that are safe? 22.9.3 · L5
    Test doubles are usually classes that mutate recorded calls, and they are passed into code that may call them from several tasks. In Swift 6 language mode a…
  2. On which thread do tests run in Swift Testing, and how do you test code isolated to the main actor? 22.9.4 · L5
    In Swift Testing, a plain test function is not run on the main thread: both a synchronous and an async plain test reported they were off the main thread in the…

More in Testing