Agent skill

swift-testing

Use when writing tests with Swift Testing (@Test,

Stars 187
Forks 13

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

swift
let user = try #require(await fetchUser(id: "123"))
#expect(user.id == "123")

Test Structure

swift
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

swift
#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

swift
@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

swift
@Test func testAsync() async throws {
    let result = try await fetchData()
    #expect(!result.isEmpty)
}

Confirmations

swift
@Test func testCallback() async {
    await confirmation("callback received") { confirm in
        let sut = SomeType { confirm() }
        sut.triggerCallback()
    }
}

Tags

swift
extension Tag {
    @Tag static var fast: Self
    @Tag static var networking: Self
}

@Test(.tags(.fast, .networking))
func testNetworkCall() { }

Common Pitfalls

  1. Overusing #require — Use #expect for most checks
  2. Forgetting state isolation — Each test gets a NEW instance
  3. Accidental Cartesian product — Always use zip for paired inputs
  4. Not using .serialized — Apply for thread-unsafe legacy tests

Common Mistakes

  1. Overusing #require#require is for preconditions only. Using it for normal assertions means the test stops at first failure instead of reporting all failures. Use #expect for assertions, #require only when subsequent assertions depend on the value.

  2. Cartesian product bugs@Test(arguments: [a, b], [c, d]) creates 4 combinations, not 2. Always use zip to pair arguments correctly: arguments: zip([a, b], [c, d]).

  3. 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.

  4. Parallel test conflicts — Swift Testing runs tests in parallel by default. Tests touching shared files, databases, or singletons will interfere. Use .serialized or isolation strategies.

  5. Not using async naturally — Wrapping async operations in Task { } defeats the purpose. Use async/await directly in test function signature: @Test func testAsync() async throws { }.

  6. Confirmation misuseconfirmation is for verifying callbacks were called. Using it for assertions is wrong. Use #expect for assertions, confirmation for callback counts.

Expand your agent's capabilities with these related and highly-rated skills.

johnrogers/claude-swift-engineering

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.

187 13
Explore
johnrogers/claude-swift-engineering

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.

187 13
Explore
johnrogers/claude-swift-engineering

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.

187 13
Explore
johnrogers/claude-swift-engineering

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.

187 13
Explore
johnrogers/claude-swift-engineering

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.

187 13
Explore
johnrogers/claude-swift-engineering

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.

187 13
Explore

Didn't find tool you were looking for?

Be as detailed as possible for better results