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/Trait | Purpose |
|---|---|
@Test | Mark function as test |
@Test("Name") | Test with display name |
@Test(arguments:) | Parameterized test |
@Suite | Group tests |
@Tag | Define custom tag |
.tags() | Apply tags |
.disabled() | Skip test |
.enabled(if:) | Conditional run |
.timeLimit() | Fail if too slow |
.bug() | Link to issue |
.serialized | Run 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."