Agent skill
swift-testing
Use when writing tests with Swift Testing (@Test,
Install this agent skill to your Project
npx add-skill https://github.com/johnrogers/claude-swift-engineering/tree/main/plugins/swift-engineering/skills/swift-testing
SKILL.md
Swift Testing Framework
Modern testing with Swift Testing framework. No XCTest.
Overview
Swift Testing replaces XCTest with a modern macro-based approach that's more concise, has better async support, and runs tests in parallel by default. The core principle: if you learned XCTest, unlearn it—Swift Testing works differently.
References
Core Concepts
Assertions
| Macro | Use Case |
|---|---|
#expect(expression) |
Soft check — continues on failure. Use for most assertions. |
#require(expression) |
Hard check — stops test on failure. Use for preconditions only. |
Optional Unwrapping
let user = try #require(await fetchUser(id: "123"))
#expect(user.id == "123")
Test Structure
import Testing
@testable import YourModule
@Suite
struct FeatureTests {
let sut: FeatureType
init() throws {
sut = FeatureType()
}
@Test("Description of behavior")
func testBehavior() {
#expect(sut.someProperty == expected)
}
}
Assertion Conversions
| XCTest | Swift Testing |
|---|---|
XCTAssert(expr) |
#expect(expr) |
XCTAssertEqual(a, b) |
#expect(a == b) |
XCTAssertNil(a) |
#expect(a == nil) |
XCTAssertNotNil(a) |
#expect(a != nil) |
try XCTUnwrap(a) |
try #require(a) |
XCTAssertThrowsError |
#expect(throws: ErrorType.self) { } |
XCTAssertNoThrow |
#expect(throws: Never.self) { } |
Error Testing
#expect(throws: (any Error).self) { try riskyOperation() }
#expect(throws: NetworkError.self) { try fetch() }
#expect(throws: NetworkError.timeout) { try fetch() }
#expect(throws: Never.self) { try safeOperation() }
Parameterized Tests
@Test("Validates inputs", arguments: zip(
["a", "b", "c"],
[1, 2, 3]
))
func testInputs(input: String, expected: Int) {
#expect(process(input) == expected)
}
Warning: Multiple collections WITHOUT zip creates Cartesian product.
Async Testing
@Test func testAsync() async throws {
let result = try await fetchData()
#expect(!result.isEmpty)
}
Confirmations
@Test func testCallback() async {
await confirmation("callback received") { confirm in
let sut = SomeType { confirm() }
sut.triggerCallback()
}
}
Tags
extension Tag {
@Tag static var fast: Self
@Tag static var networking: Self
}
@Test(.tags(.fast, .networking))
func testNetworkCall() { }
Common Pitfalls
- Overusing
#require— Use#expectfor most checks - Forgetting state isolation — Each test gets a NEW instance
- Accidental Cartesian product — Always use
zipfor paired inputs - Not using
.serialized— Apply for thread-unsafe legacy tests
Common Mistakes
-
Overusing
#require—#requireis for preconditions only. Using it for normal assertions means the test stops at first failure instead of reporting all failures. Use#expectfor assertions,#requireonly when subsequent assertions depend on the value. -
Cartesian product bugs —
@Test(arguments: [a, b], [c, d])creates 4 combinations, not 2. Always usezipto pair arguments correctly:arguments: zip([a, b], [c, d]). -
Forgetting state isolation — Swift Testing creates a new test instance per test method. BUT shared state between tests (static variables, singletons) still leak. Use dependency injection or clean up singletons between tests.
-
Parallel test conflicts — Swift Testing runs tests in parallel by default. Tests touching shared files, databases, or singletons will interfere. Use
.serializedor isolation strategies. -
Not using
asyncnaturally — Wrapping async operations inTask { }defeats the purpose. Useasync/awaitdirectly in test function signature:@Test func testAsync() async throws { }. -
Confirmation misuse —
confirmationis for verifying callbacks were called. Using it for assertions is wrong. Use#expectfor assertions,confirmationfor callback counts.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
swiftui-advanced
Use when implementing gesture composition (simultaneous, sequenced, exclusive), adaptive layouts (ViewThatFits, AnyLayout, size classes), or choosing architecture patterns (MVVM vs TCA vs vanilla, State-as-Bridge). Covers advanced SwiftUI patterns beyond basic views.
ios-hig
Use when designing iOS interfaces, implementing accessibility (VoiceOver, Dynamic Type), handling dark mode, ensuring adequate touch targets, providing animation/haptic feedback, or requesting user permissions. Apple Human Interface Guidelines for iOS compliance.
ios-26-platform
Use when implementing iOS 26 features (Liquid Glass, new SwiftUI APIs, WebView, Chart3D), deploying iOS 26+ apps, or supporting backward compatibility with iOS 17/18.
swiftui-patterns
Use when implementing iOS 17+ SwiftUI patterns: @Observable/@Bindable, MVVM architecture, NavigationStack, lazy loading, UIKit interop, accessibility (VoiceOver/Dynamic Type), async operations (.task/.refreshable), or migrating from ObservableObject/@StateObject.
grdb
Use when writing raw SQL with GRDB, complex joins across 4+ tables, window functions, ValueObservation for reactive queries, or dropping down from SQLiteData for performance. Direct SQLite access for iOS/macOS with type-safe queries and migrations.
localization
Use when implementing internationalization (i18n), String Catalogs, pluralization, or right-to-left layout support. Covers modern localization workflows with Xcode String Catalogs and LocalizedStringKey patterns.
Didn't find tool you were looking for?