mcpField
S
swift-testing

swift-testing

github.com/johnrogersTesting & quality233206 installsUpdated 41w ago
Repository

About swift-testing

A practical guide to Apple’s Swift Testing framework for Swift developers replacing XCTest. It covers macro-based assertions, async and parameterized tests, and common migration pitfalls.

Key features

  1. 01Macro-based #expect and #require assertions
  2. 02Async tests and callback confirmations
  3. 03Parameterized tests with argument pairing
  4. 04Test tags and parallel execution guidance
  5. 05XCTest assertion migration patterns

Use cases

  1. 01Replacing XCTest assertions in a Swift test suite
  2. 02Testing async functions and callback behavior
  3. 03Building paired input cases for parameterized tests

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/johnrogers/claude-swift-engineering --skill swift-testing

After installing, type/swift-testingin Claude Code to confirm it loaded.

Details

From the project README

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
MacroUse 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

XCTestSwift 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

  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 misuse — confirmation is for verifying callbacks were called. Using it for assertions is wrong. Use #expect for assertions, confirmation for callback counts.

Comments

Markdown supported