Overview

Swift Testing is the framework for new unit and integration tests in Swift 6 and Xcode 16 or later. It uses macros instead of an XCTestCase hierarchy and runs tests in parallel by default. XCTest stays necessary for UI automation and performance measurement. Swift 6.4 (Xcode 27) lets XCTest assertions work inside Swift Testing tests and #expect inside XCTest cases, so migration can be gradual. Language-agnostic rules are in testing.

Write tests as functions in struct suites

Mark a function @Test, group related tests in a struct marked @Suite, and assert with #expect. The runner creates a new instance of the suite for each test, so stored properties are fresh state, and init() and deinit replace setUp and tearDown. Prefer structs over classes for suites; value semantics keep tests isolated.

import Testing
 
@Suite struct InvoiceTests {
    let calculator = TotalCalculator()
 
    @Test("Total excludes voided line items")
    func totalExcludesVoided() {
        let invoice = Invoice(lines: [.init(cents: 500), .init(cents: 300, isVoid: true)])
        #expect(calculator.total(for: invoice) == 500)
    }
 
    @Test(arguments: [(0, 1), (1, 1), (5, 120)])
    func factorial(n: Int, expected: Int) {
        #expect(Factorial.of(n) == expected)
    }
}

Choose #expect or #require by what should happen on failure

#expect(x == y) records a failure and keeps running, which shows every broken assertion in one run. try #require(...) stops the test, so use it for preconditions the rest of the test depends on, such as unwrapping an optional (let first = try #require(items.first)). Use Issue.record("reason") where you would have used XCTFail.

Parameterize instead of looping

Pass a collection to @Test(arguments:) so each element runs as its own test case with its own pass or fail. A for loop inside one test reports a single result.

Expect parallel execution; serialize deliberately

Tests run in parallel by default, where XCTest ran a suite serially. Tests that share a global, a file, or a database need isolation, or @Suite(.serialized) to run in order. Fix shared state first; serializing hides it.

Use confirmations for callbacks and events

confirmation replaces XCTestExpectation and XCTWaiter for asserting that an event fired a set number of times. Prefer async code that awaits the result directly, and use confirmations only for callback or delegate APIs.

Keep XCTest for what Swift Testing lacks

Swift Testing has no replacement for XCUIApplication UI tests, XCTMetric performance tests, or Objective-C test classes. XCTAssertEqual(_:_:accuracy:) has no direct equivalent; use isApproximatelyEqual from swift-numerics. Run both frameworks side by side in one target and migrate suites as you touch them.

Use the Swift 6.2 additions where they fit

Exit tests verify that code terminates under specific conditions (such as a failed precondition), and attachments add strings, images, or logs to a result. Both arrived in Swift 6.2.