Testing Mastery

Swift Testing Attributes: The Complete Reference

January 19, 2026
5 min read
Featured image for blog post: Swift Testing Attributes: The Complete Reference

Swift Testing Attributes: The Complete Reference

Swift Testing uses attributes and traits to declare tests, organize them, and control their behavior. Here's everything you can use.

Follow along with the code: iOS-Practice on GitHub

@Test

Marks a function as a test:

@Test func fetchReturnsData() async throws {
    // test code
}

Display Names

Give tests human-readable names:

@Test("Returns weather data for valid city")
func fetchReturnsData() async throws { }

This name appears in Xcode's test navigator and test reports.

Parameterized Tests

Run the same test with different inputs:

@Test(arguments: ["NYC", "LA", "Tokyo"])
func fetchWeather(city: String) async throws { }

Multiple parameters:

@Test(arguments: zip(["NYC", "LA"], [72, 85]))
func weatherMatches(city: String, temp: Int) async throws { }

@Suite

Groups tests and applies shared configuration:

@Suite("Weather Manager Tests")
struct WeatherManagerTests {
    @Test func cachesFreshData() { }
    @Test func expiresOldData() { }
}

Nested Suites

Create hierarchical organization:

@Suite("Weather Manager")
struct WeatherManagerTests {

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

    @Suite("Errors")
    struct ErrorTests {
        @Test func handlesNetworkError() { }
    }
}

@Tag

Define custom tags for filtering:

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

Apply tags to tests or suites:

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

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

@Suite(.tags(.critical))
struct AuthenticationTests { }

Traits

Traits modify test behavior. Pass them to @Test or @Suite.

.disabled

Skip tests with an explanation:

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

Conditionally disable:

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

.enabled

The opposite—only run when a condition is true:

@Test(.enabled(if: hasNetworkAccess))
func networkTest() async { }

.timeLimit

Fail if the test exceeds a duration:

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

@Test(.timeLimit(.minutes(1)))
func canBeSlow() async { }

.bug

Link tests to issue trackers:

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

@Test(.bug(id: "JIRA-456", "Login fails on iOS 17"))
func trackedIssue() { }

.tags

Apply tags (covered above):

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

.serialized

Force tests to run serially (not in parallel):

@Suite(.serialized)
struct DatabaseTests {
    @Test func insertsRecord() { }
    @Test func deletesRecord() { }
}

By default, Swift Testing runs tests in parallel.

Combining Traits

Chain multiple traits:

@Test(
    "Downloads large file successfully",
    .tags(.networking, .slow),
    .timeLimit(.minutes(2)),
    .bug(id: "NET-123")
)
func downloadLargeFile() async throws { }

Apply traits to an entire suite:

@Suite(
    "Integration Tests",
    .tags(.integration),
    .serialized
)
struct IntegrationTests { }

Macros

#expect

Assert conditions:

#expect(result == expected)
#expect(value > 0)
#expect(array.isEmpty)

#expect with errors

Assert that code throws:

// Throws specific error
#expect(throws: WeatherError.cityNotFound) {
    try service.fetch("FakeCity")
}

// Throws any error
#expect(throws: (any Error).self) {
    try dangerousOperation()
}

// Throws nothing
#expect(throws: Never.self) {
    try safeOperation()
}

// Custom error validation
#expect {
    try service.fetch("test")
} throws: { error in
    (error as? NetworkError)?.code == 404
}

#require

Like #expect, but stops the test on failure:

let user = try #require(await fetchUser()) // Fails test if nil
#expect(user.name == "Alice") // Only runs if above passed

Issue.record

Record a failure without stopping:

Issue.record("Something went wrong")
Issue.record("Validation failed", sourceLocation: #_sourceLocation)

Quick Reference Table

Attribute/TraitPurpose
@TestMark function as test
@Test("Name")Test with display name
@Test(arguments:)Parameterized test
@SuiteGroup tests
@TagDefine custom tag
.tags()Apply tags
.disabled()Skip test
.enabled(if:)Conditional run
.timeLimit()Fail if too slow
.bug()Link to issue
.serializedRun serially
#expect()Assert condition
#require()Assert or stop

Interview tip: "Swift Testing's trait system is composable—I can add time limits, bug references, and tags to the same test. The #expect(throws:) syntax is much cleaner than XCTest's do/catch/XCTFail pattern for testing errors."

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