Testing Mastery

Swift Testing Killer Features: Beyond Cleaner Syntax

January 19, 2026
6 min read
Featured image for blog post: Swift Testing Killer Features: Beyond Cleaner Syntax

Swift Testing Killer Features: Beyond Cleaner Syntax

Swift Testing isn't just cleaner syntax—it has features that XCTest simply doesn't have. Let's explore the capabilities that make it worth adopting.

Follow along with the code: iOS-Practice on GitHub

Parameterized Tests

This is the feature I use most. Run the same test with different inputs:

@Test("Fetches weather for various cities", arguments: ["NYC", "LA", "Tokyo", "London", "Paris"])
func fetchesForCity(city: String) async throws {
    let weather = try await sut.getWeather(for: city)
    #expect(weather.city == city)
}

This runs 5 times, once per city. Each run appears separately in Xcode's test navigator—if Tokyo fails, you see "Tokyo" failed, not just "fetchesForCity failed."

In XCTest, you'd need 5 separate test methods or a loop that hides which case failed.

Multiple Parameters

You can test combinations too:

@Test(arguments: [
    (city: "NYC", temp: 72),
    (city: "LA", temp: 85),
    (city: "Tokyo", temp: 68)
])
func weatherMatchesExpected(city: String, temp: Int) async throws {
    let result = try await service.fetch(city)
    #expect(result.temperature == temp)
}

Tags: Filter Your Test Runs

Define custom tags and apply them to tests:

extension Tag {
    @Tag static var networking: Self
    @Tag static var slow: Self
    @Tag static var critical: Self
}

@Test(.tags(.networking))
func fetchFromAPI() async { }

@Test(.tags(.networking, .slow))
func downloadLargeFile() async { }

@Test(.tags(.critical))
func userAuthentication() { }

Run only networking tests in CI, or skip slow tests during local development. Tags propagate to nested tests too.

Test Suites: Organize with Nested Structs

Group related tests hierarchically:

@Suite("Weather Manager")
struct WeatherManagerTests {

    @Suite("Caching")
    struct CachingTests {
        @Test func cachesFreshData() { }
        @Test func expiresOldData() { }
    }

    @Suite("Error Handling")
    struct ErrorTests {
        @Test func handlesNetworkError() { }
        @Test func handlesCityNotFound() { }
    }
}

This creates a tree structure in Xcode's test navigator. Suites share setup through their init(), and nested suites inherit parent setup.

Traits: Control Test Behavior

Disabled Tests

@Test(.disabled("API not deployed yet"))
func newFeature() async { }

@Test(.disabled(if: isCI, "Flaky on CI"))
func flakyTest() { }

Time Limits

@Test(.timeLimit(.seconds(5)))
func mustBeFast() async { }

If the test takes longer than 5 seconds, it fails. Great for catching performance regressions.

Bug References

Link tests to issue trackers:

@Test(.bug("https://github.com/org/repo/issues/123"))
func regressionTest() { }

@Test(.bug(id: "JIRA-456"))
func trackedIssue() { }

Confirmations: Better Async Expectations

For callback-based APIs, use confirmation instead of XCTest's expectation:

// XCTest
func testCallback() {
    let exp = expectation(description: "called")
    service.fetch { _ in
        exp.fulfill()
    }
    wait(for: [exp], timeout: 5)
}

// Swift Testing
@Test func callback() async {
    await confirmation { confirm in
        service.fetch { _ in
            confirm()
        }
    }
}

Expect multiple callbacks? Just specify the count:

@Test func multipleCallbacks() async {
    await confirmation(expectedCount: 3) { confirm in
        service.fetchAll { _ in
            confirm()
        }
    }
}

When to Use Which

ScenarioRecommendation
New test filesSwift Testing
Parameterized testsSwift Testing (XCTest can't)
UI tests (XCUITest)XCTest only
Performance testsXCTest (measure blocks)
Pre-Xcode 16 supportXCTest only
Existing test suiteMigrate gradually

Interview tip: "I use Swift Testing for new tests because parameterized tests and tags let me write less code while testing more scenarios. The test navigator shows each parameter combination separately, so I immediately see which specific case failed."

Originally published on deadpixel.llc

© 2026 Christopher Moore / Dead Pixel Studio

Let's work together

Professional discovery, design, and complete technical coverage for your ideas

Get in touch