Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
Swift Testing
https://github.com/swiftlang/swift-testing
Admin
Swift Testing is a package with expressive and intuitive APIs that make testing your Swift code a
...
Tokens:
41,613
Snippets:
509
Trust Score:
8.8
Update:
1 week ago
Context
Skills
Chat
Benchmark
91.4
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Swift Testing Swift Testing is a modern, macro-based testing framework for the Swift programming language, developed as part of Swift.org and included with the Swift 6 toolchain and Xcode 16. It provides expressive, intuitive APIs built on Swift macros that let you declare complex test behaviors with minimal code. The framework integrates deeply with Swift Concurrency, runs tests in parallel by default, and is fully cross-platform — supporting Apple platforms, Linux, Windows, FreeBSD, and experimentally Wasm, Android, and OpenBSD. The framework's core design philosophy centers on three pillars: a clear macro-based API (`@Test`, `@Suite`, `#expect`, `#require`) that captures runtime values for diagnostic output; a flexible trait system for customizing test behavior at declaration time; and seamless integration with existing XCTest-based test suites. Tests are discovered automatically, run concurrently via Swift structured concurrency, and report rich diagnostic information including captured expression values, backtraces, and collection diffs. --- ## `@Test` — Declare a Test Function The `@Test` attached macro declares a function as a test. It accepts an optional display name and zero or more traits. The annotated function can be `async`, `throws`, or both, and may live at the top level or inside a type (suite). ```swift import Testing // Basic test @Test func additionWorks() { #expect(1 + 1 == 2) } // Named test with async/throws @Test("User login succeeds with valid credentials") func loginSucceeds() async throws { let session = try await AuthService.login(user: "alice", password: "s3cr3t") #expect(session.isValid) #expect(session.userID == "alice") } // Test inside a suite type struct MathTests { @Test func multiplicationIsCommutative() { let a = 6, b = 7 #expect(a * b == b * a) // Expectation failed: (a → 6) * (b → 7) == (b → 7) * (a → 6) } } ``` --- ## `@Suite` — Declare a Test Suite The `@Suite` attached macro marks a type as a test suite and assigns it an optional display name and traits. Any type containing `@Test` functions is implicitly a suite; `@Suite` is needed only when you want to attach traits or a custom display name. ```swift import Testing @Suite("Food Truck Tests", .tags(.ordering, .payment)) struct FoodTruckTests { let truck = FoodTruck() @Test("Order is fulfilled within time limit") func orderFulfillment() async throws { let order = Order(items: [.burger, .fries]) let receipt = try await truck.place(order) #expect(receipt.isFulfilled) } @Suite("Menu Tests") struct MenuTests { @Test func menuIsNotEmpty() { #expect(!FoodTruck().menu.isEmpty) } } } ``` --- ## `#expect` — Non-Throwing Assertion Macro `#expect` evaluates a Boolean condition and records an issue if it fails, capturing the evaluated sub-expression values so you can see exactly what went wrong. It does not throw and does not abort the test on failure. ```swift import Testing @Test func expectExamples() { let name = "Swift" // Boolean condition — captures both sides on failure #expect(name.count == 5) // Expectation failed: (name.count → 5) == 5 ✓ // Optional check (non-nil) let value: Int? = nil #expect(value != nil) // records issue, test continues // Type check let anyValue: Any = 42 #expect(anyValue is Int) // Collection equality with diff output let got = [1, 2, 4] let want = [1, 2, 3] #expect(got == want) // Expectation failed: (got → [1, 2, 4]) == [1, 2, 3] // inserted [4], removed [3] } // Expecting a specific error type to be thrown @Test func divisionByZero() { #expect(throws: DivisionError.self) { try divide(10, by: 0) } } // Expecting no errors (Never.self) @Test func neverThrows() { #expect(throws: Never.self) { let _ = try safeOperation() } } // Matching a specific error instance @Test func specificErrorThrown() { #expect(throws: NetworkError.timeout) { try fetchWithTimeout(url: URL(string: "https://example.com")!, timeout: 0) } } // Custom error predicate @Test func customErrorMatching() async { await #expect { try await fetchResource() } throws: { error in guard let networkError = error as? NetworkError else { return false } return networkError.statusCode == 404 } } ``` --- ## `#require` — Throwing Assertion Macro `#require` works like `#expect` but throws `ExpectationFailedError` on failure, immediately stopping the test. It also unwraps optionals, returning the non-optional value on success. ```swift import Testing @Test func requireExamples() async throws { // Unwrap an optional — test stops if nil let video = try #require(await videoLibrary.video(named: "A Beach")) // `video` is non-optional here #expect(video.duration > .seconds(30)) // Require a cast succeeds let shape: any Shape = Circle(radius: 5) let circle = try #require(shape as? Circle) #expect(circle.radius == 5) // Require a condition holds before continuing let results = try await searchAPI.query("swift") try #require(!results.isEmpty, "Search returned no results for 'swift'") #expect(results.first!.title.contains("Swift")) } ``` --- ## Parameterized Tests — `@Test(arguments:)` Pass a collection or two collections to run the same test function once per element. Parameterized test cases run in parallel by default and each case is independently tracked. ```swift import Testing // Single-collection parameterization @Test("Parse valid dates", arguments: [ "2024-01-15", "2023-12-31", "2000-02-29" ]) func parseDate(_ dateString: String) throws { let date = try #require(DateParser.parse(dateString)) #expect(date > Date(timeIntervalSince1970: 0)) } // Two-collection (cartesian product) @Test("Multiplication table", arguments: [1, 2, 3], [10, 20, 30]) func multiplicationTable(a: Int, b: Int) { #expect(a * b == b * a) // commutative #expect(a * b > 0) // positive result } // Zipped collections (paired, not cartesian) @Test("City and country pairs", arguments: zip( ["Tokyo", "Paris", "Cairo"], ["Japan", "France", "Egypt"] )) func cityCountryPair(city: String, country: String) async throws { let result = try await GeoAPI.lookup(city: city) #expect(result.country == country) } ``` --- ## `confirmation` — Verify Events in Callbacks `confirmation` verifies that an event occurs a specified number of times inside a closure. Use it when `#expect` cannot observe asynchronous callbacks or delegate methods. ```swift import Testing // Expect exactly N confirmations @Test func bakesCorrectNumber() async { let n = 5 await confirmation("Baked \(n) buns", expectedCount: n) { bunBaked in foodTruck.onBake = { item in if item == .cinnamonBun { bunBaked() } } await foodTruck.bake(.cinnamonBun, count: n) } } // Expect event never occurs @Test func noCancellationOnSuccess() async { await confirmation("Order cancelled", expectedCount: 0) { orderCancelled in orderManager.onCancel = { orderCancelled() } await orderManager.processAll() } } // Expect event occurs within a range (Swift 6.1+) @Test func bakesWithinRange() async { await confirmation("Buns baked", expectedCount: 3...7) { bunBaked in foodTruck.onBake = { _ in bunBaked() } await foodTruck.bakeTray() } } ``` --- ## `withKnownIssue` — Expected Failures `withKnownIssue` wraps code that is expected to fail (e.g., a known bug). If the issue occurs it is recorded but does not fail the test; if the issue does NOT occur a secondary issue is recorded to indicate the known issue was resolved. ```swift import Testing @Test func knownIssueExamples() throws { // Unconditionally known issue — all failures inside are expected withKnownIssue { try flakyLegacyFunction() } // Conditional: only known on certain platforms withKnownIssue("Crashes on Linux due to #12345") { try platformSpecificCode() } when: { ProcessInfo.processInfo.operatingSystem == .linux } // Selective matching — only specific error types are "known" try withKnownIssue("FileNotFound expected until assets ship") { try loadAssets() } matching: { issue in issue.error is FileNotFoundError } // Async variant await withKnownIssue("Network call is flaky in CI") { try await unreliableNetworkCall() } // Intermittent — no secondary issue if known issue doesn't occur withKnownIssue("Sometimes fails", isIntermittent: true) { try intermittentOperation() } } ``` --- ## `.enabled(if:)` / `.disabled` Traits — Conditional Tests `ConditionTrait` controls whether a test runs at all, based on a runtime condition or an unconditional flag. Apply via `Trait.enabled(if:)`, `Trait.enabled(_:)`, `Trait.disabled(_:)`, or `Trait.disabled(if:)`. ```swift import Testing // Run only if a feature flag is enabled @Test(.enabled(if: FeatureFlags.isNewCheckoutEnabled)) func newCheckoutFlow() async throws { let cart = Cart(items: [.coffee]) let receipt = try await checkout(cart) #expect(receipt.total > 0) } // Always skip with a descriptive comment @Test(.disabled("Waiting for backend team to fix #4521")) func flakyIntegrationTest() async throws { try await callUnstableEndpoint() } // Disable based on OS version @Test(.disabled(if: ProcessInfo.processInfo.operatingSystemVersion.majorVersion < 14, "Requires macOS 14+")) func modernAPITest() { #expect(ModernAPI.isAvailable) } // Async condition @Test(.enabled("Feature enabled remotely") { await RemoteConfig.shared.isEnabled("new_feature") }) func remotelyEnabledTest() async throws { try await exerciseNewFeature() } ``` --- ## `.tags(_:)` — Organizing Tests with Tags Tags are custom `Tag` values declared as static members in a `Tag` extension using the `@Tag` macro. Apply them with `Trait.tags(_:)` to filter or group tests. ```swift import Testing // Declare reusable tags extension Tag { @Tag static var networking: Self @Tag static var ui: Self @Tag static var database: Self @Tag static var performance: Self } // Apply tags to tests @Test("Fetch user profile", .tags(.networking)) func fetchUserProfile() async throws { let profile = try await UserService.fetchProfile(id: "user123") #expect(profile.name.isEmpty == false) } @Suite("Database Tests", .tags(.database)) struct DBTests { @Test("Write and read record", .tags(.database, .performance)) func writeAndRead() throws { let db = try TestDatabase() try db.write(User(id: 1, name: "Alice")) let user = try db.read(id: 1) #expect(user?.name == "Alice") } } // Run only networking tests: swift test --filter Testing.Tag/networking ``` --- ## `.bug(_:)` — Tracking Bug Reports Annotate tests with associated bug tracker URLs or IDs using `Trait.bug(_:_:)`. This provides traceability between test failures and filed issues. ```swift import Testing @Test(.bug("https://github.com/myorg/myrepo/issues/42", "Title parsing fails for Unicode")) func titleParsingUnicode() { let parser = TitleParser() #expect(parser.parse("Héllo Wörld") == "Héllo Wörld") } // Bug with numeric ID only (no URL) @Test(.bug(id: 98765, "Crash when scrolling rapidly")) func rapidScrollTest() async { let view = await createScrollView() await view.scrollRapidly(iterations: 100) // no crash = test passes } // Bug with both URL and ID @Test(.bug("https://radar.apple.com/", id: "FB12345678", "Memory leak in image cache")) func imageCacheMemoryLeak() { let cache = ImageCache() for _ in 0..<1000 { _ = cache.image(named: "test") } #expect(cache.memoryUsage < 50_000_000) } ``` --- ## `.timeLimit(_:)` — Test Timeouts `TimeLimitTrait` cancels a test that runs too long and records a failure. The minimum unit is one minute. When applied to a suite it cascades to all contained tests. ```swift import Testing // Apply to individual test (minimum: 1 minute) @Test(.timeLimit(.minutes(2))) func networkOperationCompletes() async throws { let data = try await LongRunningService.fetchLargeDataset() #expect(data.count > 0) } // Apply to entire suite — each test in the suite may run up to 1 minute @Suite(.timeLimit(.minutes(1))) struct SlowTests { @Test func backgroundSync() async throws { try await BackgroundSyncManager.sync() } @Test func exportLargeFile() async throws { let path = try await Exporter.export(records: largeDataset) #expect(FileManager.default.fileExists(atPath: path)) } } ``` --- ## `.serialized` — Serial Execution `ParallelizationTrait` forces a parameterized test or suite to run serially instead of in parallel. ```swift import Testing // Serialize a parameterized test's cases @Test("Database migrations", .serialized, arguments: ["001", "002", "003", "004"]) func applyMigration(_ version: String) throws { // Migrations must run in order; cases are not parallelized try MigrationRunner.apply(version: version) #expect(MigrationRunner.currentVersion == version) } // Serialize all tests within a suite @Suite("File System Tests", .serialized) struct FileSystemTests { @Test func createFile() throws { try FileManager.default.createFile(atPath: "/tmp/test.txt", contents: nil) #expect(FileManager.default.fileExists(atPath: "/tmp/test.txt")) } @Test func deleteFile() throws { try FileManager.default.removeItem(atPath: "/tmp/test.txt") #expect(!FileManager.default.fileExists(atPath: "/tmp/test.txt")) } } ``` --- ## `Attachment` — Attaching Data to Tests The `Attachment` API saves diagnostic artifacts (strings, binary data, images) alongside test output. Types conforming to `Attachable` can be recorded using `Attachment.record(_:named:)`. ```swift import Testing @Test func generatesValidReport() async throws { let report = try await ReportGenerator.generate() // Attach the raw report for inspection on failure Attachment.record(report.pdfData, named: "report.pdf") Attachment.record(report.jsonPayload, named: "report.json") #expect(report.pageCount == 5) #expect(report.title == "Monthly Summary") } @Test func rendersImage() async throws { let renderer = ChartRenderer(data: salesData) let image = try await renderer.render() // Attach image for visual inspection (requires _Testing_CoreGraphics overlay) Attachment.record(image, named: "sales_chart.png") #expect(image.width == 800) #expect(image.height == 600) } // Custom Attachable type struct JSONReport: Attachable { let payload: String func withUnsafeBytes<R>( for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R ) throws -> R { var data = Array(payload.utf8) return try data.withUnsafeMutableBytes { try body(UnsafeRawBufferPointer($0)) } } } @Test func customAttachable() { let report = JSONReport(payload: #"{"status":"ok"}"#) Attachment.record(report, named: "status.json") #expect(report.payload.contains("ok")) } ``` --- ## `#expect(processExitsWith:)` — Exit Tests Exit tests verify that a closure causes the current process to exit with an expected status (e.g., a `fatalError`, `preconditionFailure`, or explicit `exit()`). Available on Swift 6.2+ / Xcode 26+. ```swift import Testing @Test func fatalErrorOnInvalidInput() async { await #expect(processExitsWith: .failure) { // This runs in a subprocess; a fatalError causes .failure let _ = UnsafePointer<Int>(bitPattern: 0)!.pointee } } @Test func exitCodeOnMissingArgument() async { await #expect(processExitsWith: .exitCode(2)) { CommandLineTool.main(arguments: []) // exits with code 2 on bad args } } @Test func signalOnCrash() async { await #expect(processExitsWith: .signal(SIGSEGV)) { triggerSegfault() } } ``` --- ## Custom Traits via `Trait` / `TestScoping` Create reusable setup/teardown logic by conforming a type to `TestTrait`/`SuiteTrait` and `TestScoping`. The `provideScope` method wraps each test case execution. ```swift import Testing // A trait that injects a test database into every test struct WithTestDatabase: TestTrait, SuiteTrait, TestScoping { var isRecursive: Bool { true } func provideScope( for test: Test, testCase: Test.Case?, performing function: @Sendable () async throws -> Void ) async throws { let db = try await TestDatabase.setUp() defer { Task { try? await db.tearDown() } } try await function() } } extension Trait where Self == WithTestDatabase { static var withTestDatabase: Self { WithTestDatabase() } } // Use the custom trait @Suite(.withTestDatabase) struct UserRepositoryTests { @Test func createUser() async throws { let repo = UserRepository() let user = try await repo.create(name: "Bob") #expect(user.id != nil) } @Test func deleteUser() async throws { let repo = UserRepository() try await repo.delete(id: "existing-user") let found = try await repo.find(id: "existing-user") #expect(found == nil) } } ``` --- ## Summary Swift Testing is best suited for two broad categories of use. **Unit and integration testing** of Swift packages and applications benefits from its concise macro syntax, rich failure diagnostics (captured expression values, collection diffs), parameterized tests to cover edge cases with minimal boilerplate, and `withKnownIssue` to cleanly track regressions or known flaws without suppressing the entire test suite. The `confirmation` API fills the gap left by traditional assertion helpers when dealing with callbacks, notifications, and asynchronous delegate patterns that cannot be awaited inline. **CI and tooling integration** is equally well served: tests run in parallel by default using Swift Concurrency, with `ParallelizationTrait` and `TimeLimitTrait` providing guardrails for shared-state tests and long-running operations. Tags enable selective execution (`swift test --filter`) and structured reporting. The `Attachment` API lets tests save diagnostic artifacts (images, JSON payloads, binary blobs) that tools can surface in test result browsers. Because Swift Testing ships with the Swift 6 toolchain and runs alongside XCTest without any package dependency, it is a zero-friction addition to any Swift project on Apple platforms, Linux, Windows, or FreeBSD.