2026 Developer ToolsSwift
WWDC26 · 21 min · Developer Tools / Swift
Migrate to Swift Testing
Learn how to fearlessly adopt Swift Testing alongside your XCTests using test framework interoperability. Discover best practices and patterns for incrementally introducing advanced testing features that accelerate development and increase coverage.
Watch at developer.apple.com ↗Chapters
Code shown on screen · 10 snippets
Name a test using a raw identifier
import Testing
@testable import DemoApp
func `Default climate: tropical`() async throws {
let fruit = Fruit(name: "Coconut")
#expect(fruit.climate == .tropical)
} Wrap XCTFail in a test helper function
func testUniqueFruitNames() async throws {
assertUnique(Market.fruits + [Fruit.lychee])
}
// TestHelpers.swift
func assertUnique(_ fruits: [Fruit], file: StaticString = #filePath, line: UInt = #line) {
var uniqueNames = Set<String>()
for name in fruits.map(\.name) {
if !uniqueNames.insert(name).inserted {
XCTFail("Duplicate name: \(name)", file: file, line: line)
}
}
} Replace XCTFail with Issue.record in the test helper
import Testing
func assertUnique(_ fruits: [Fruit], sourceLocation: SourceLocation = ...) {
var uniqueNames = Set<String>()
for name in fruits.map(\.name) {
if !uniqueNames.insert(name).inserted {
Issue.record("Duplicate name: \(name)", sourceLocation: sourceLocation)
}
}
} Run Swift Package tests with the strict interoperability mode from Terminal
> SWIFT_TESTING_XCTEST_INTEROP_MODE=strict swift test Common migration: skipping tests
let isFall = false
// XCTest
func testSwallowFallMigration() async throws {
try XCTSkipIf(!isFall, "Wrong season for migration")
// ...
}
// Test.cancel interoperability from Swift Testing
func testSwallowFallMigration() async throws {
if !isFall {
try Test.cancel("Wrong season for migration")
}
// ...
}
// ✅ Prefer test trait in Swift Testing
(.enabled(if: isFall, "Wrong season for migration"))
func `Swallow fall migration`() async throws {
// ...
} Common migration: halting after test failures
func testExample() async throws {
#expect(Fruit.banana.climate == .temperate)
try #require(Fruit.banana == Fruit.plantain)
XCTFail("This is never reached")
} Example of nested loops which can be converted into a parameterized @Test function
struct BirdTests {
func `Birds flap wings successfully`() async throws {
for bird in Aviary.birds {
for count in (40...100) {
try await bird.flapWings(count: count)
}
}
}
} Refactor nested loops into a parameterized @Test function
struct BirdTests {
(arguments: Aviary.birds, 40...100)
func `Birds flap wings successfully`(bird: Bird, count: Int) async throws {
try await bird.flapWings(count: count)
}
} Precondition check on empty input name in an initializer
// In `Bird.init(...)`
if name.isEmpty {
preconditionFailure("Bird name cannot be empty")
} Add coverage for precondition failure with exit test
extension BirdTests {
func `Bird with empty name crashes`() async throws {
await #expect(processExitsWith: .failure) {
_ = Bird(name: "")
}
}
} Related sessions
-
33 min -
24 min -
27 min