These are the lesson notes for this day. Its build-along tutorial and flash cards have not been written yet.
A test function can be async, so testing asynchronous code is mostly straightforward: await the call, then check the result. In Swift Testing you write @Test func loads() async throws { … } and check with #expect. XCTest supports async test methods in the same way. For code that still uses callbacks, Swift Testing provides confirmation { }, and XCTest provides XCTestExpectation with await fulfillment(of:).
What makes asynchronous code testable is what makes any code testable: its dependencies are passed in. A model that calls URLSession.shared directly cannot be tested without a network. A model that is handed a fetch function can be given a stub. Swift 6.4 adds test cancellation and warning-level issues, and lets Swift Testing and XCTest be mixed in one target while you migrate.
struct WeatherModel {
let fetch: () async throws -> Int // the dependency is passed in
func summary() async -> String {
do {
return "It is \(try await fetch()) degrees"
} catch {
return "Unavailable"
}
}
}
@Test func showsTheTemperature() async {
let model = WeatherModel(fetch: { 72 }) // a stub, not the network
#expect(await model.summary() == "It is 72 degrees")
}
@Test func showsAFallbackWhenTheFetchFails() async {
let model = WeatherModel(fetch: { throw URLError(.notConnectedToInternet) })
#expect(await model.summary() == "Unavailable")
}
Exercise
Write tests for a model that loads data: one for success and one for failure, using a stubbed fetch function. Then test a callback-based API using confirmation.
async function?confirmation or an expectation?