Swift Testing
Overview
Swift Testing replaces XCTest with a modern macro-based approach: @Test instead of test-prefixed methods, #expect/#require instead of XCTAssert*, structs instead of XCTestCase subclasses, and parallel-by-default execution instead of serial. If you learned XCTest, unlearn it — Swift Testing works differently in ways that matter (state isolation, argument combinatorics, actor behavior).
Use this skill to write, review, migrate, and debug Swift tests with modern Swift Testing APIs. Prioritize readable tests, robust parallel execution, clear diagnostics, and incremental migration from XCTest where needed. When asked to review code, report only genuine problems — do not nitpick or invent issues.
Agent behavior contract
- Prefer Swift Testing for all new Swift unit and integration tests, but keep XCTest for UI automation (
XCUIApplication), performance metrics (XCTMetric), and Objective-C-only test code — Swift Testing does not support UI tests.
- Treat
#expect as the default assertion; use #require only when a later line depends on the value being present/true (precondition, not assertion).
- Default to parallel-safe guidance. If tests aren't isolated, propose fixing the shared state first rather than reaching for
.serialized.
- Prefer traits for behavior and metadata (
.enabled, .disabled, .timeLimit, .bug, tags) over naming conventions or ad-hoc comments.
- Recommend parameterized tests when multiple tests share logic and differ only in input values — but watch argument-count combinatorics (see
references/parameterized-testing.md).
- Use
@available on individual test functions for OS-gated behavior, never on suite types.
- Keep migration advice incremental: convert assertions first, then organize suites, then introduce parameterization/traits. Don't rewrite an existing XCTest suite to Swift Testing unless asked.
- Only import
Testing in test targets, never in app/library/binary targets.
- Target Swift 6.2+ and modern Swift concurrency by default, and use a project structure with folder layout that mirrors the code under test (see
references/writing-better-tests.md).
- Swift Testing gains new features every Swift release (3-4/year), so your training data is likely stale on recent APIs and Apple's own docs can lag too. Treat the user's installed toolchain as authoritative for what's available; see
references/new-features.md for capabilities that may postdate your training.
First 60 seconds (triage template)
- Clarify the goal: new tests, migration, flaky failures, performance, CI filtering, or async waiting.
- Collect minimal facts:
- Xcode/Swift version and platform targets
- Whether tests currently use XCTest, Swift Testing, or both
- Whether failures are deterministic or flaky
- Whether tests access shared resources (database, files, network, global state)
- Branch quickly:
- repetitive tests -> parameterized tests
- noisy or flaky failures -> known-issue handling and test isolation
- migration questions -> XCTest mapping and coexistence strategy
- async callback complexity -> continuation/confirmation patterns
Routing map (read the right reference fast)
- Test building blocks, suite structure, struct-vs-class, zero-arg init ->
references/fundamentals.md
#expect, #require, throw expectations, Issue.record(), readable failures -> references/expectations.md
- Traits, tags, bug linking, conditions, availability ->
references/traits-and-tags.md
- Parameterized test design, combinatorics,
zip pitfalls -> references/parameterized-testing.md
- Async/await tests, confirmations, callback bridging, actor isolation,
.serialized nuances, mocking -> references/async-testing.md
- Default parallel execution, random order, suite-level isolation strategy ->
references/parallelization-and-isolation.md
- Test speed, determinism, and flakiness prevention ->
references/performance-and-best-practices.md
- Test hygiene: FIRST principles, structuring tests, hidden dependencies, testing SwiftUI view models ->
references/writing-better-tests.md
- Recent Swift Testing features (raw identifiers, exit tests, attachments, test scopes,
ConditionTrait.evaluate()) -> references/new-features.md
- XCTest coexistence and migration workflow ->
references/migration-from-xctest.md
- Xcode test navigator/report workflows and diagnostics ->
references/xcode-workflows.md
If doing partial work (e.g. only a migration pass, or only an async-tests review), load only the relevant reference files instead of all of them.
Common pitfalls -> next best move
- Repetitive
testFooCaseA/testFooCaseB/... methods -> replace with one parameterized @Test(arguments:) (references/parameterized-testing.md).
- Two argument collections without
zip -> silently becomes a Cartesian product, not pairwise; use zip, or better, an array of tuples/dictionary to avoid zip's silent-truncation and enum-reordering fragility (references/parameterized-testing.md).
- Failing optional preconditions hidden in later assertions ->
try #require(...) then assert on the unwrapped value (references/expectations.md).
- Overusing
#require for ordinary assertions -> stops the test at first failure instead of reporting all failures; reserve it for preconditions (references/expectations.md).
#expect(!isLoggedIn) -> ! defeats macro expansion and produces unhelpful failure output; write #expect(isLoggedIn == false) instead (references/expectations.md).
- "Each test gets a fresh instance, so state can't leak" -> true for instance properties, false for
static/singleton state; isolate or reset it (references/parallelization-and-isolation.md).
- Flaky integration tests on shared database -> isolate dependencies or use in-memory repositories; use
.serialized only as a transition step (references/parallelization-and-isolation.md).
.serialized "should" work on any test -> it only affects a parameterized test's own cases when applied directly to a single @Test; applied to a @Suite it serializes everything inside that suite (references/async-testing.md).
.timeLimit(.seconds(10)) -> wrong; the trait only accepts .minutes(...) (references/async-testing.md).
- Wrapping async work in
Task { } inside a test, or using a completion closure with confirmation() -> defeats the point; use async test functions directly, or track the Task and await it (references/async-testing.md).
confirmation() used for general assertions -> it's for verifying callback/event counts, not a substitute for #expect (references/async-testing.md).
- Disabled tests that silently rot -> prefer
withKnownIssue (optionally isIntermittent: true) over blanket disabling so they keep signaling (references/expectations.md).
- Unclear failure output for complex types -> conform to
CustomTestStringConvertible in the test target only (references/expectations.md).
- Test-plan include/exclude by test name -> use tags and tag-based filters instead (
references/traits-and-tags.md, references/xcode-workflows.md).
- Expected value derived from the same expression as the code under test, or
if/switch branching inside a parameterized test body -> both let the test mirror/mask bugs in production logic instead of verifying it independently (references/parameterized-testing.md).
- Testing a SwiftUI
View directly -> flaky and implementation-coupled; test the view model instead (references/writing-better-tests.md).
- Hidden dependencies (
URLSession.shared, ambient UserDefaults) baked into production code -> inject them so tests can substitute fakes (references/writing-better-tests.md).
Reviewing or writing test code
When asked to review Swift Testing code, organize findings by file. For each issue:
- State the file and relevant line(s).
- Name the rule being violated.
- Show a brief before/after code fix.
Skip files with no issues. End with a prioritized summary of the most impactful changes to make first.
When asked to write or improve tests, follow the same rules above but make the changes directly instead of returning a findings report.
Example finding:
### UserTests.swift
**Line 5: Use struct, not class, for test suites.**
// Before
class UserTests: XCTestCase {
// After
struct UserTests {
**Line 30: Use `#require` for preconditions, not `#expect`.**
// Before
#expect(users.isEmpty == false)
let first = users.first!
// After
let first = try #require(users.first)
### Summary
1. **Fundamentals (high):** Test suite on line 5 should be a struct, not a class inheriting from `XCTestCase`.
2. **Assertions (medium):** Force-unwrap on line 30 should use `#require` to unwrap safely and stop the test early on failure.
Verification checklist
- Each test has a single clear behavior and an expressive display name when needed.
- Prerequisites use
#require where failure should stop the test; ordinary assertions use #expect.
- Repeated logic is parameterized instead of duplicated, with concrete (not derived) expected values.
- Tests are parallel-safe, or intentionally
.serialized with a stated reason.
- Async code is awaited natively (not
Task { }-wrapped), and callback APIs are bridged safely.
- Hidden dependencies (networking,
UserDefaults, time, randomness) are injected, not ambient.
- Migration keeps unsupported XCTest-only scenarios (UI tests,
XCTMetric) on XCTest.
- Test file/folder structure mirrors the production code it covers.
References
references/fundamentals.md
references/expectations.md
references/traits-and-tags.md
references/parameterized-testing.md
references/async-testing.md
references/parallelization-and-isolation.md
references/performance-and-best-practices.md
references/writing-better-tests.md
references/new-features.md
references/migration-from-xctest.md
references/xcode-workflows.md
1---2name: swift-testing3description: Use whenever writing, reviewing, improving, migrating, or debugging Swift tests. Covers @Test,4---56# Swift Testing78## Overview910Swift Testing replaces XCTest with a modern macro-based approach: `@Test` instead of `test`-prefixed methods, `#expect`/`#require` instead of `XCTAssert*`, structs instead of `XCTestCase` subclasses, and parallel-by-default execution instead of serial. If you learned XCTest, unlearn it — Swift Testing works differently in ways that matter (state isolation, argument combinatorics, actor behavior).1112Use this skill to write, review, migrate, and debug Swift tests with modern Swift Testing APIs. Prioritize readable tests, robust parallel execution, clear diagnostics, and incremental migration from XCTest where needed. When asked to review code, report only genuine problems — do not nitpick or invent issues.1314- [Apple Documentation](https://developer.apple.com/documentation/testing)15- [Migration Guide](https://steipete.me/posts/2025/migrating-700-tests-to-swift-testing)1617## Agent behavior contract18191. Prefer Swift Testing for all new Swift unit and integration tests, but keep XCTest for UI automation (`XCUIApplication`), performance metrics (`XCTMetric`), and Objective-C-only test code — Swift Testing does not support UI tests.202. Treat `#expect` as the default assertion; use `#require` only when a later line depends on the value being present/true (precondition, not assertion).213. Default to parallel-safe guidance. If tests aren't isolated, propose fixing the shared state first rather than reaching for `.serialized`.224. Prefer traits for behavior and metadata (`.enabled`, `.disabled`, `.timeLimit`, `.bug`, tags) over naming conventions or ad-hoc comments.235. Recommend parameterized tests when multiple tests share logic and differ only in input values — but watch argument-count combinatorics (see `references/parameterized-testing.md`).246. Use `@available` on individual test *functions* for OS-gated behavior, never on suite types.257. Keep migration advice incremental: convert assertions first, then organize suites, then introduce parameterization/traits. Don't rewrite an existing XCTest suite to Swift Testing unless asked.268. Only import `Testing` in test targets, never in app/library/binary targets.279. Target Swift 6.2+ and modern Swift concurrency by default, and use a project structure with folder layout that mirrors the code under test (see `references/writing-better-tests.md`).2810. Swift Testing gains new features every Swift release (3-4/year), so your training data is likely stale on recent APIs and Apple's own docs can lag too. Treat the user's installed toolchain as authoritative for what's available; see `references/new-features.md` for capabilities that may postdate your training.2930## First 60 seconds (triage template)3132- Clarify the goal: new tests, migration, flaky failures, performance, CI filtering, or async waiting.33- Collect minimal facts:34 - Xcode/Swift version and platform targets35 - Whether tests currently use XCTest, Swift Testing, or both36 - Whether failures are deterministic or flaky37 - Whether tests access shared resources (database, files, network, global state)38- Branch quickly:39 - repetitive tests -> parameterized tests40 - noisy or flaky failures -> known-issue handling and test isolation41 - migration questions -> XCTest mapping and coexistence strategy42 - async callback complexity -> continuation/confirmation patterns4344## Routing map (read the right reference fast)4546- Test building blocks, suite structure, struct-vs-class, zero-arg init -> `references/fundamentals.md`47- `#expect`, `#require`, throw expectations, `Issue.record()`, readable failures -> `references/expectations.md`48- Traits, tags, bug linking, conditions, availability -> `references/traits-and-tags.md`49- Parameterized test design, combinatorics, `zip` pitfalls -> `references/parameterized-testing.md`50- Async/await tests, confirmations, callback bridging, actor isolation, `.serialized` nuances, mocking -> `references/async-testing.md`51- Default parallel execution, random order, suite-level isolation strategy -> `references/parallelization-and-isolation.md`52- Test speed, determinism, and flakiness prevention -> `references/performance-and-best-practices.md`53- Test hygiene: FIRST principles, structuring tests, hidden dependencies, testing SwiftUI view models -> `references/writing-better-tests.md`54- Recent Swift Testing features (raw identifiers, exit tests, attachments, test scopes, `ConditionTrait.evaluate()`) -> `references/new-features.md`55- XCTest coexistence and migration workflow -> `references/migration-from-xctest.md`56- Xcode test navigator/report workflows and diagnostics -> `references/xcode-workflows.md`5758If doing partial work (e.g. only a migration pass, or only an async-tests review), load only the relevant reference files instead of all of them.5960## Common pitfalls -> next best move6162- Repetitive `testFooCaseA/testFooCaseB/...` methods -> replace with one parameterized `@Test(arguments:)` (`references/parameterized-testing.md`).63- Two argument collections without `zip` -> silently becomes a Cartesian product, not pairwise; use `zip`, or better, an array of tuples/dictionary to avoid `zip`'s silent-truncation and enum-reordering fragility (`references/parameterized-testing.md`).64- Failing optional preconditions hidden in later assertions -> `try #require(...)` then assert on the unwrapped value (`references/expectations.md`).65- Overusing `#require` for ordinary assertions -> stops the test at first failure instead of reporting all failures; reserve it for preconditions (`references/expectations.md`).66- `#expect(!isLoggedIn)` -> `!` defeats macro expansion and produces unhelpful failure output; write `#expect(isLoggedIn == false)` instead (`references/expectations.md`).67- "Each test gets a fresh instance, so state can't leak" -> true for instance properties, false for `static`/singleton state; isolate or reset it (`references/parallelization-and-isolation.md`).68- Flaky integration tests on shared database -> isolate dependencies or use in-memory repositories; use `.serialized` only as a transition step (`references/parallelization-and-isolation.md`).69- `.serialized` "should" work on any test -> it only affects a parameterized test's own cases when applied directly to a single `@Test`; applied to a `@Suite` it serializes everything inside that suite (`references/async-testing.md`).70- `.timeLimit(.seconds(10))` -> wrong; the trait only accepts `.minutes(...)` (`references/async-testing.md`).71- Wrapping async work in `Task { }` inside a test, or using a completion closure with `confirmation()` -> defeats the point; use `async` test functions directly, or track the `Task` and `await` it (`references/async-testing.md`).72- `confirmation()` used for general assertions -> it's for verifying callback/event counts, not a substitute for `#expect` (`references/async-testing.md`).73- Disabled tests that silently rot -> prefer `withKnownIssue` (optionally `isIntermittent: true`) over blanket disabling so they keep signaling (`references/expectations.md`).74- Unclear failure output for complex types -> conform to `CustomTestStringConvertible` in the test target only (`references/expectations.md`).75- Test-plan include/exclude by test name -> use tags and tag-based filters instead (`references/traits-and-tags.md`, `references/xcode-workflows.md`).76- Expected value derived from the same expression as the code under test, or `if`/`switch` branching inside a parameterized test body -> both let the test mirror/mask bugs in production logic instead of verifying it independently (`references/parameterized-testing.md`).77- Testing a SwiftUI `View` directly -> flaky and implementation-coupled; test the view model instead (`references/writing-better-tests.md`).78- Hidden dependencies (`URLSession.shared`, ambient `UserDefaults`) baked into production code -> inject them so tests can substitute fakes (`references/writing-better-tests.md`).7980## Reviewing or writing test code8182When asked to review Swift Testing code, organize findings by file. For each issue:83841. State the file and relevant line(s).852. Name the rule being violated.863. Show a brief before/after code fix.8788Skip files with no issues. End with a prioritized summary of the most impactful changes to make first.8990When asked to write or improve tests, follow the same rules above but make the changes directly instead of returning a findings report.9192Example finding:9394```95### UserTests.swift9697**Line 5: Use struct, not class, for test suites.**9899// Before100class UserTests: XCTestCase {101// After102struct UserTests {103104**Line 30: Use `#require` for preconditions, not `#expect`.**105106// Before107#expect(users.isEmpty == false)108let first = users.first!109// After110let first = try #require(users.first)111112### Summary1131. **Fundamentals (high):** Test suite on line 5 should be a struct, not a class inheriting from `XCTestCase`.1142. **Assertions (medium):** Force-unwrap on line 30 should use `#require` to unwrap safely and stop the test early on failure.115```116117## Verification checklist118119- Each test has a single clear behavior and an expressive display name when needed.120- Prerequisites use `#require` where failure should stop the test; ordinary assertions use `#expect`.121- Repeated logic is parameterized instead of duplicated, with concrete (not derived) expected values.122- Tests are parallel-safe, or intentionally `.serialized` with a stated reason.123- Async code is awaited natively (not `Task { }`-wrapped), and callback APIs are bridged safely.124- Hidden dependencies (networking, `UserDefaults`, time, randomness) are injected, not ambient.125- Migration keeps unsupported XCTest-only scenarios (UI tests, `XCTMetric`) on XCTest.126- Test file/folder structure mirrors the production code it covers.127128## References129130- `references/fundamentals.md`131- `references/expectations.md`132- `references/traits-and-tags.md`133- `references/parameterized-testing.md`134- `references/async-testing.md`135- `references/parallelization-and-isolation.md`136- `references/performance-and-best-practices.md`137- `references/writing-better-tests.md`138- `references/new-features.md`139- `references/migration-from-xctest.md`140- `references/xcode-workflows.md`