
swift-testing
Writes and migrates Swift Testing framework tests with @Test, @Suite, expect, require, confirmation, traits, withKnownIssue, Attachment.record, processExitsWith exit tests and capture lists, Test.cancel, Issue.record warnings/manual failures, XCTest-to-Swift Testing migration, Xcode 27 interoperability modes, XCUITest UI-test boundaries, performance/snapshot boundaries, mocking, async patterns, and test organization. Use when writing tests, converting XCTest assertions such as XCTUnwrap or XCTFail, reviewing advanced Swift Testing API availability, or deciding when to keep XCTest/XCUITest.
- Installs
- 4.3K
- Last 24h
- +23
- Trending rank
- #5142
- Security
- Pass
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swift-testing- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads swift-testing automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
Security audits
Independent scans of the skill's instructions and bundled files.
SKILL.md
The instructions your agent reads when the skill is triggered.
Swift Testing
Swift Testing is the modern testing framework for Swift (Xcode 16+, Swift 6+). Prefer it for new unit tests. Keep XCTest where migration is still in progress, and use XCTest for UI automation, performance APIs, Objective-C exception tests, and common snapshot-test tooling.
Contents
- Basic Tests
@Test Traits- #expect and #require
@Suite and Test Organization- Execution Model
- XCTest Migration Boundaries
- Known Issues
- Additional Patterns
- Common Mistakes
- Test Attachments
- Exit Testing
- Version-Gated APIs
- Review Checklist
- References
Basic Tests
import Testing
@Test("User can update their display name")
func updateDisplayName() {
var user = User(name: "Alice")
user.name = "Bob"
#expect(user.name == "Bob")
}
@Test Traits
@Test("Validates email format") // display name
@Test(.tags(.validation, .email)) // tags
@Test(.disabled("Server migration in progress")) // disabled
@Test(.enabled(if: ProcessInfo.processInfo.environment["CI"] != nil)) // conditional
@Test(.bug("https://github.com/org/repo/issues/42")) // bug reference
@Test(.timeLimit(.minutes(1))) // time limit
@Test("Timeout handling", .tags(.networking), .timeLimit(.seconds(30))) // combined
#expect and #require
// #expect records failure but continues execution
#expect(result == 42)
#expect(name.isEmpty == false)
#expect(items.count > 0, "Items should not be empty")
// #expect with error type checking
#expect(throws: ValidationError.self) {
try validate(email: "not-an-email")
}
// #expect with specific error value
#expect {
try validate(email: "")
} throws: { error in
guard let err = error as? ValidationError else { return false }
return err == .empty
}
// #require records failure AND stops test (like XCTUnwrap)
let user = try #require(await fetchUser(id: 1))
#expect(user.name == "Alice")
// #require for optionals -- unwraps or fails
let first = try #require(items.first)
#expect(first.isValid)
Rule: Use #require when subsequent assertions depend on the value. Use #expect for independent checks.
@Suite and Test Organization
See references/testing-patterns.md for suite organization, confirmation patterns, known-issue handling, and execution-model details.
Execution Model
Swift Testing runs tests in parallel by default. Do not assume test order, shared suite instances, or exclusive access to mutable state unless you explicitly design for it.
@Suite(.serialized)
struct KeychainTests {
@Test func storesToken() throws { /* ... */ }
@Test func deletesToken() throws { /* ... */ }
}
Use .serialized when a test or suite must run one-at-a-time because it touches shared external state. It does not make unrelated tests outside that scope run serially.
Rules:
- Each test must set up its own state.
- Shared mutable globals are a bug unless protected or intentionally serialized.
@Suite(.serialized)is for exclusive execution, not for expressing logical ordering between tests.- If tests depend on sequence, combine them into one test or move the sequence into shared helper code.
XCTest Migration Boundaries
Swift Testing unit tests do not inherit from XCTestCase. Declare @Test on
free functions or methods on suite types such as struct, class, or actor;
use static or class methods when instance fixtures are unnecessary.
XCTest and Swift Testing can coexist during migration. Migrate one file or suite at a time, compare discovery/pass/fail/skip counts, and keep UI automation, performance benchmarks, and common snapshot flows on XCTest/XCUITest or snapshot tooling. Separate files or targets when that makes runner expectations clearer.
For Xcode 27-era mixed helpers, check the configured interoperability mode rather than claiming cross-framework APIs are forbidden. Older test plans inherit limited; new projects use complete; strict and none are also available. Prefer complete or strict during migration and use SWIFT_TESTING_XCTEST_INTEROP_MODE for SwiftPM when needed. See references/testing-advanced.md for the mode matrix and toolchain gates.
Do not mechanically replace every XCTest assertion with #expect; preserve
required unwraps and unconditional failures with these migration defaults:
XCTAssert*->#expect(...)XCTUnwrapor any value required by later checks ->try #require(...)XCTFail("...")or manual unconditional issues ->Issue.record("...")- UI tests, performance benchmarks, and common snapshot-test flows stay on XCTest/XCUITest or snapshot tooling.
- Put
@availableon individual@Testfunctions, not on suite types or their containing types.
See references/testing-patterns.md for migration examples and references/testing-advanced.md for Swift/Xcode version gates.
Known Issues
Mark expected failures so they do not cause test failure:
withKnownIssue("Propane tank is empty") {
#expect(truck.grill.isHeating)
}
// Intermittent / flaky failures
withKnownIssue(isIntermittent: true) {
#expect(service.isReachable)
}
// Conditional known issue
withKnownIssue {
#expect(foodTruck.grill.isHeating)
} when: {
!hasPropane
}
If no known issues are recorded, Swift Testing records a distinct issue notifying you the problem may be resolved.
Additional Patterns
See references/testing-patterns.md for parameterized tests, tags and suites, async testing, traits, and execution-model details.
Test Attachments
Attach diagnostic data to test results for debugging failures. See references/testing-patterns.md for full examples.
@Test func generateReport() async throws {
let report = try generateReport()
Attachment.record(report.data, named: "report.json")
#expect(report.isValid)
}
For image attachments and their toolchain gate, use the canonical table in Version-Gated APIs.
Exit Testing
Test code that calls exit(), fatalError(), or preconditionFailure() on a
supported runtime. State the exact gate from Version-Gated APIs
when correcting exit-test code.
@Test func invalidInputCausesExit() async {
await #expect(processExitsWith: .failure) {
processInvalidInput() // calls fatalError()
}
}
Version-Gated APIs
For advanced APIs, state the exact toolchain and runtime gate beside the correction. This is the canonical summary; references/testing-advanced.md contains the detailed matrix and examples.
@Test func exitsWithCapturedCode() async {
let expectedCode: Int32 = 42
await #expect(processExitsWith: .failure) { [expectedCode] in
exit(expectedCode)
}
}
| User code to correct | Current guidance |
|---|---|
#expect(exitsWith:) | Use await #expect(processExitsWith: .failure) { ... }. Exit testing requires Swift 6.2 / Xcode 26.0 or newer and is supported on macOS, Linux, FreeBSD, OpenBSD, and Windows runtime targets, not iOS, tvOS, or watchOS. For an iOS app target, test fatal-path logic through a smaller non-exiting API or a supported host/tool target. |
| Exit-test closure reads outer values | Add an explicit capture list, for example { [expectedCode] in ... }. Exit-test capture lists require the Swift 6.3 compiler; captured values must be Sendable and Codable. |
Test.cancel() in a test that awaits work | Make the test async throws and call try Test.cancel("reason"). Test.cancel(_:) requires Swift 6.3 / Xcode 26.4-era support. |
Issue.record(..., severity: .warning) | Use Issue.record("message", severity: .warning). Warning severity is reported but does not fail the test, and requires Swift 6.3 / Xcode 26.4-era support. |
Attachment(image, named:).record() | Use Attachment.record(image, named: "name", as: .png). Import Testing plus the relevant image framework; Apple-platform image values include UIImage, CGImage, CIImage, and NSImage. Image attachment recording requires Swift 6.3 / Xcode 26.4-era support. |
Common Mistakes
- Testing implementation, not behavior. Test what the code does, not how.
- No error path tests. If a function can throw, test the throw path.
- Flaky async tests or sleeps. Use
confirmation, clock injection, or concurrency primitives instead of sleeping. - Shared mutable state between tests. Each test sets up its own state via
init()in@Suite. - Missing accessibility identifiers in UI tests. XCUITest queries rely on them.
- Not testing cancellation. If code supports
Taskcancellation, verify it cancels cleanly. - Unclear migration boundaries. Follow XCTest Migration Boundaries instead of treating either framework as an all-or-nothing choice.
- Non-Sendable helpers shared across tests. Make shared helpers
Sendable; annotate MainActor-dependent test code with@MainActor. - Treating serialization as ordering. Tests run in parallel by default;
.serializedprotects exclusive state but does not make one test feed another.
Review Checklist
- All new tests use Swift Testing (
@Test,#expect), not XCTest assertions - Test names describe behavior (
fetchUserReturnsNilOnNetworkErrornottestFetchUser) - Error paths have dedicated tests
- Async tests use
confirmation(), notTask.sleep - Parameterized tests used for repetitive variations
- Tags applied for filtering (
.critical,.slow) - Mocks conform to protocols, not subclass concrete types
- No shared mutable state between tests
- Tests do not rely on declaration order or shared suite instances
-
.serializedused only for truly exclusive state, not to model workflow sequencing - Cancellation tested for cancellable async operations
References
- Testing patterns: references/testing-patterns.md
- Advanced testing (warnings, cancellation, image attachments): references/testing-advanced.md
FAQ
What does the swift-testing skill do?
Writes and migrates Swift Testing framework tests with @Test, @Suite, expect, require, confirmation, traits, withKnownIssue, Attachment.record, processExitsWith exit tests and capture lists, Test.cancel, Issue.record warnings/manual failures, XCTest-to-Swift Testing migration, Xcode 27 interoperability modes, XCUITest UI-test boundaries, performance/snapshot boundaries, mocking, async patterns, and test organization. Use when writing tests, converting XCTest assertions such as XCTUnwrap or XCTFail, reviewing advanced Swift Testing API availability, or deciding when to keep XCTest/XCUITest.
How do I install swift-testing?
Run npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swift-testing in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/swift-testing/ (personal) or .claude/skills/swift-testing/ (project).
Which AI agents can use swift-testing?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is swift-testing safe to install?
Security partners have reviewed it: Gen Agent Trust Hub passed; Socket passed; Snyk passed; ZeroLeaks passed. As with any skill, read the SKILL.md and bundled scripts before installing.
Who maintains swift-testing?
It is published by dpearson2699 in dpearson2699/swift-ios-skills. It has 4,343 recorded installs.
More from dpearson2699/swift-ios-skills
swiftui-animation
dpearson2699/swift-ios-skills
Implement, diagnose, or review SwiftUI motion using explicit and scoped implicit animations, springs, transitions, PhaseAnimator, KeyframeAnimator, matched geometry or navigation zoom, SF Symbol effects, and custom Animation types. Use when views should animate on state changes, insertion, removal, navigation, or multi-step choreography, or when motion must respect Reduce Motion and Swift concurrency.
ios-accessibility
dpearson2699/swift-ios-skills
Build and audit SwiftUI, UIKit, and AppKit accessibility for VoiceOver, Voice Control, Switch Control, Full Keyboard Access, Dynamic Type, focus restoration, labels/traits/actions, traversal, custom rotors, NSAccessibility, XCTest checks, adaptive system preferences, and App Store accessibility declarations. Use when implementing accessible UI, fixing an accessibility audit, testing assistive-technology behavior, or substantiating App Store Accessibility Nutrition Labels.
swiftui-performance
dpearson2699/swift-ios-skills
Profile, diagnose, and remediate SwiftUI runtime performance using code review, Instruments, and repeatable measurements. Use when a SwiftUI screen renders slowly, scrolling or animations hitch, view bodies update excessively, list identity churns, layout work spikes, or broad Observation dependencies raise CPU cost. Covers evidence-based triage, SwiftUI Instruments lanes, lazy-container guardrails, state lifetime, and before/after verification.
swiftui-patterns
dpearson2699/swift-ios-skills
Builds and reviews SwiftUI views with modern MV architecture, state, composition, isolated previews, and migration guidance. Covers @Observable ownership, @State/@Bindable/@Environment wiring, view decomposition, ViewModifiers, environment values, .task loading, iOS 26+ handoffs, Writing Tools, clipboard availability, and performance. Use when structuring SwiftUI state, managing @Observable, composing views, previewing meaningful UI states, or correcting SwiftUI patterns.
app-store-review
dpearson2699/swift-ios-skills
Audits App Store submission readiness and rejection risk across current review guidelines, PrivacyInfo.xcprivacy and required-reason APIs, privacy labels, ATT, StoreKit payments, metadata, entitlements, widgets, and Live Activities. Use when preparing a submission, responding to rejection, reconciling privacy evidence, or separating upload blockers from cleanup.
ios-networking
dpearson2699/swift-ios-skills
Build, review, or improve networking code in iOS/macOS apps using URLSession with async/await, structured concurrency, and modern Swift patterns. Use when working with REST APIs, downloading files, uploading data, WebSocket connections, pagination, retry logic, request middleware, caching, background transfers, or network reachability monitoring. Also use when handling HTTP requests, API clients, network error handling, or data fetching in Swift apps.
Popular testing & qa skills
tdd
mattpocock/skills
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
grilling
mattpocock/skills
Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.
migrate-to-shoehorn
mattpocock/skills
Migrate test files from as type assertions to @total-typescript/shoehorn. Use when user mentions shoehorn, wants to replace as in tests, or needs partial test data.
run-train
lllllllama/rigorpilot-skills
Rigor Train skill for deep learning research repositories. Use when a documented or selected training command should be run conservatively for startup verification, short-run verification, full kickoff, or resume, with command, config, seed, log, checkpoint, status, and metric evidence written to standardized trainoutputs/. Do not use for environment setup, exploratory sweeps, speculative idea implementation, or end-to-end orchestration.
test-driven-development
obra/superpowers
Use when implementing any feature or bugfix, before writing implementation code
finishing-a-development-branch
obra/superpowers
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work