Expert guidance on Swift Testing for writing, reviewing, migrating, and debugging tests. Use when developers mention Swift Testing, @Test, @Suite,
license
MIT
Write and review Swift Testing code for correctness, modern API usage, and adherence to project conventions. Report only genuine problems — do not nitpick or invent issues.
First 60 seconds (triage template)
Before diving in, clarify the goal and collect minimal facts:
Goal: new tests, migration, flaky failures, performance, CI filtering, or async waiting?
Xcode/Swift version and platform targets
Tests currently using XCTest, Swift Testing, or both?
Failures deterministic or flaky?
Tests accessing shared resources (database, files, network, global state)?
If doing partial work, load only the relevant reference files.
Core Instructions
Target Swift 6.2 or later, using modern Swift concurrency.
Prefer Swift Testing for all new unit and integration tests; help migrate existing XCTest code when asked.
Swift Testing does not support UI tests — keep XCUIApplication on XCTest. Also keep XCTMetric performance tests and Objective-C-only test code on XCTest.
Only import Testing in test targets, never in app/library/binary targets.
Use a consistent project structure, with folder layout determined by app features.
Treat #expect as the default assertion; use #require when subsequent lines depend on a prerequisite value or when you need hard-stop semantics.
Default to parallel-safe guidance. If tests are not isolated, first propose fixing shared state before applying .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.
Use @available on test functions for OS-gated behavior instead of runtime #available checks inside test bodies; never annotate suite types with @available.
Keep migration advice incremental: convert assertions first, then organize suites, then introduce parameterization/traits.
Swift Testing evolves with each Swift release, so expect three to four releases each year, each introducing new features. Training data will naturally be outdated. Treat the user's installed toolchain as authoritative, but note that Apple's documentation about the APIs is often stale — handle it carefully.
Agent Behavior Contract
Use Swift Testing framework (@Test, #expect, #require, @Suite) for all new tests, not XCTest.
Always structure tests with clear Arrange-Act-Assert phases.
Use proper test double terminology per Martin Fowler's taxonomy (Dummy, Fake, Stub, Spy, SpyingStub, Mock). What the Swift community often calls a "Mock" is usually a SpyingStub.
Place fixtures close to models with #if DEBUG, not in test targets.
Place test doubles close to interfaces with #if DEBUG, not in test targets.
Prefer state verification over behavior verification — simpler, less brittle tests.
Use #expect for soft assertions (continue on failure) and #require for hard assertions (stop on failure).
@TestfunccalculateTotal() {
// Givenlet cart =ShoppingCart()
cart.add(Item(price: 10))
cart.add(Item(price: 20))
// Whenlet total = cart.calculateTotal()
// Then
#expect(total ==30)
}
Common pitfalls → next best move
Repetitive testFooCaseA/testFooCaseB/... methods → one parameterized @Test(arguments:).
Failing optional preconditions hidden in later assertions → try #require(...) then assert on the unwrapped value.
Flaky integration tests on shared database → isolate dependencies or use in-memory repositories; use .serialized only as a transition step.
Disabled tests that silently rot → prefer withKnownIssue for temporary known failures to preserve signal.
Unclear failure values for complex types → conform the type to CustomTestStringConvertible for focused diagnostics.
Test-plan include/exclude by names → use tags and tag-based filters instead.
.serialized is scope-dependent: on a bare non-parameterized @Test func it has no effect; on a parameterized @Test it serializes the argument cases; on @Suite(.serialized) it serializes all the suite's contained tests (parameterized or not) and sub-suites relative to each other. See references/async-tests.md.
.timeLimit(.seconds(...)) → only .minutes(...) is accepted.
Unsafe mutable counters captured by async callbacks → use an actor or thread-safe container.
Verification checklist
Each test has a single clear behavior and an expressive display name where needed.
Prerequisites use #require where failure should stop the test.
Repeated logic is parameterized instead of duplicated.
Tests are parallel-safe or intentionally serialized with rationale.
Async code is awaited and callback APIs are bridged safely.
Fixtures use sensible defaults, not random values.
Test doubles are minimal (only stub what's needed).
Migration preserves XCTest-only scenarios (UI, XCTMetric, ObjC-only) on XCTest.
Output Format
If the user asks for a review, 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.
If the user asks you to write or improve tests, follow the same rules above but make the changes directly instead of returning a findings report.
references/async-tests.md — serialized tests, confirmation(), time limits, actor isolation, pre-concurrency code, networking mocks, cancellation, callback bridging, legacy-waiting anti-patterns.
references/chunked-parallel-mock-drain.md — drain mock outboxes one stanza at a time when the system under test fans out via stride-chunked withTaskGroup; waitForSent(count: N) deadlocks at chunk boundaries.
references/integration-suite-patterns.md — cross-suite serialization, nested @Suite enums for layered test targets, swift test --filter regex semantics, async teardown without defer, credential gating.