Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

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 ↗

Transcript all transcripts

Chapters

Code shown on screen · 10 snippets

Name a test using a raw identifier swift · at 1:12 ↗
import Testing

@testable import DemoApp

@Test func `Default climate: tropical`() async throws {
    let fruit = Fruit(name: "Coconut")
    #expect(fruit.climate == .tropical)
}
Wrap XCTFail in a test helper function swift · at 5:03 ↗
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 swift · at 10:12 ↗
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 bash · at 12:15 ↗
> SWIFT_TESTING_XCTEST_INTEROP_MODE=strict swift test
Common migration: skipping tests swift · at 13:10 ↗
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
@Test(.enabled(if: isFall, "Wrong season for migration"))
func `Swallow fall migration`() async throws {
   // ...
}
Common migration: halting after test failures swift · at 13:41 ↗
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 swift · at 15:57 ↗
struct BirdTests {

    @Test 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 swift · at 16:47 ↗
struct BirdTests {

    @Test(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 swift · at 18:21 ↗
// In `Bird.init(...)`
if name.isEmpty {
    preconditionFailure("Bird name cannot be empty")
}
Add coverage for precondition failure with exit test swift · at 19:27 ↗
extension BirdTests {

    @Test func `Bird with empty name crashes`() async throws {
        await #expect(processExitsWith: .failure) {
            _ = Bird(name: "")
        }
    }

}