← All shorts
69Xcode 16+2 min read

Swift Testing: @Test, #expect, and Parameterised Tests

Swift Testing replaces XCTest's class-and-assertion ceremony with free functions, macros, and native parameterisation.

XCTest ties every test to a class, a test-prefixed method name, and an assertion that halts the method on first failure — fine for years, but verbose for the common case of "run this logic against a handful of inputs."

#The old way

import XCTest

final class PriceFormatterTests: XCTestCase {
    func testFormatsWholeDollars() {
        let formatter = PriceFormatter()
        XCTAssertEqual(formatter.format(cents: 500), "$5.00")
    }

    func testFormatsFractionalCents() {
        let formatter = PriceFormatter()
        XCTAssertEqual(formatter.format(cents: 599), "$5.99")
    }
}

#The new way

import Testing

struct PriceFormatterTests {
    @Test("Formats cents as dollars", arguments: [
        (500, "$5.00"),
        (599, "$5.99"),
        (0, "$0.00")
    ])
    func formatsCents(cents: Int, expected: String) {
        let formatter = PriceFormatter()
        #expect(formatter.format(cents: cents) == expected)
    }

    @Test
    func throwsOnNegativeCents() {
        let formatter = PriceFormatter()
        #expect(throws: PriceError.self) {
            try formatter.validate(cents: -100)
        }
    }
}

#Why it matters

  • arguments: gives real parameterised tests — one test body runs once per input, each reported as its own pass/fail, instead of one method looping over cases
  • #expect keeps evaluating past a failed check within the same test instead of aborting like XCTAssert, so one run surfaces every failure, not just the first
  • Plain structs replace XCTestCase subclassing — no test-prefixed method names, no inherited setup/teardown boilerplate
  • #expect(throws:) checks an error type or a specific error value inline, without a do/catch scaffold

#Gotcha

Swift Testing runs test methods within a suite concurrently by default, unlike XCTest's serial-by-default execution — tests that share mutable state (a singleton, a shared file, a static var) can interfere with each other in ways that only show up intermittently. Mark a suite @Suite(.serialized) if its tests can't run in parallel. Swift Testing and XCTest coexist in the same target, but UI testing (XCUIApplication) and performance tests (XCTMetric) still require XCTest — Swift Testing doesn't replace those. Check exact API details against current Xcode docs, since the framework is still evolving quickly.

swift-testingtestingxcode

Related shorts