Get the FREE Ultimate OpenClaw Setup Guide →

swift-testing

npx machina-cli add skill jeremieb/swift-unit-test-instructions/swift-testing --openclaw
Files (1)
SKILL.md
5.6 KB

Swift Testing

Instructions

Step 1: Determine Testing Mode

Check CLAUDE.md for Testing Mode:

  • enterprise → consult references/enterprise.md for strict rules
  • indie → consult references/indie.md for pragmatic rules
  • If not set: ask the user which mode applies

Step 2: Identify What to Test

Ask if not already clear:

  • Which type / function / feature needs tests?
  • What is the expected behavior (not implementation)?
  • Are there async operations?
  • Are there existing mocks or does the skill need to create them?

Always test:

  • ViewModels (state changes, actions, error states)
  • Services and Repositories (business logic, data transformations)
  • Formatters, validators, utilities
  • Async flows end-to-end (with mocked dependencies)
  • Edge cases and error paths

Never test:

  • Private implementation details
  • SwiftUI view tree structure or layout positions
  • UIViewController internal wiring

Step 3: Choose the Framework

ScenarioFramework
New unit tests (Swift 5.9+, iOS 16+)Swift Testing (@Test, #expect)
Legacy or team convention on XCTestXCTest (XCTestCase, XCTAssert*)
UI / end-to-end testsXCTest + XCUITest
Async unit testsBoth support async — use await

Do NOT mix Swift Testing and XCTest in the same test target.

Step 4: Write Tests

Naming (both frameworks):

test_when_<Condition>_should_<ExpectedOutcome>()

Structure — Swift Testing:

import Testing
@testable import MyApp

@MainActor
struct LoginViewModelTests {

    @Test func test_when_credentialsAreValid_should_setIsLoggedIn() async throws {
        // Arrange
        let authService = MockAuthService(willSucceed: true)
        let sut = LoginViewModel(authService: authService)

        // Act
        await sut.login(email: "user@example.com", password: "secret")

        // Assert
        #expect(sut.isLoggedIn == true)
        #expect(sut.error == nil)
    }

    @Test func test_when_credentialsAreInvalid_should_setError() async throws {
        // Arrange
        let authService = MockAuthService(willSucceed: false)
        let sut = LoginViewModel(authService: authService)

        // Act
        await sut.login(email: "bad@example.com", password: "wrong")

        // Assert
        #expect(sut.isLoggedIn == false)
        #expect(sut.error != nil)
    }
}

Structure — XCTest:

import XCTest
@testable import MyApp

@MainActor
final class LoginViewModelTests: XCTestCase {

    func test_when_credentialsAreValid_should_setIsLoggedIn() async throws {
        // Arrange
        let authService = MockAuthService(willSucceed: true)
        let sut = LoginViewModel(authService: authService)

        // Act
        await sut.login(email: "user@example.com", password: "secret")

        // Assert
        XCTAssertTrue(sut.isLoggedIn)
        XCTAssertNil(sut.error)
    }
}

Step 5: Generate Mocks

Always generate a mock alongside any test that needs one:

// Always inject via protocol, never use real implementations in unit tests
final class MockAuthService: AuthServiceProtocol {
    let willSucceed: Bool
    private(set) var loginCallCount = 0

    init(willSucceed: Bool) {
        self.willSucceed = willSucceed
    }

    func login(email: String, password: String) async throws -> User {
        loginCallCount += 1
        if willSucceed {
            return User(id: "1", email: email)
        } else {
            throw AuthError.invalidCredentials
        }
    }
}

Step 6: Determinism Checklist

Before finalizing, verify every test:

  • No real network calls
  • No sleep() or polling
  • No shared global state between tests
  • No real time/date dependency (inject Date or a clock protocol)
  • Fresh sut instance per test
  • setUp/tearDown only when truly necessary

Examples

Example 1: ViewModel with async fetch

User says: "Write tests for my ProductListViewModel that fetches products from an API"

Actions:

  1. Read ProductListViewModel.swift to understand state and dependencies
  2. Identify the ProductRepositoryProtocol or create it if missing
  3. Generate MockProductRepository with stubbable results
  4. Write tests: empty state, loaded state, error state, loading indicator

Example 2: Data transformation

User says: "Test my PriceFormatter that converts cents to currency string"

Actions:

  1. Read PriceFormatter.swift
  2. Write Swift Testing @Test functions for: zero cents, typical value, large value, locale edge cases

Troubleshooting

Async tests fail intermittently: You are using sleep() or polling. Replace with async/await and mocked async dependencies that resolve immediately.

Tests break on refactoring: You are testing implementation details (private properties, internal call counts). Refocus on observable output state only.

ViewModel is untestable: Dependencies are created inside the type (NetworkService()). Inject them via init. This is an architecture issue — see swift-architecture-audit skill.

"Cannot find type X in scope": Add @testable import YourModule at the top of the test file.

Source

git clone https://github.com/jeremieb/swift-unit-test-instructions/blob/main/skills/swift-testing/SKILL.mdView on GitHub

Overview

Swift Testing provides unit and UI test coverage for Swift, SwiftUI, and UIKit projects, aligning with enterprise or indie standards based on CLAUDE.md. It guides what to test (ViewModels, services, formatters), when to test, and how to generate mocks for isolated, reliable tests.

How This Skill Works

Start by inspecting CLAUDE.md to determine the Testing Mode (enterprise or indie). Then identify what to test and select the appropriate framework (Swift Testing or XCTest). Write tests using conventional naming and generate mocks injected via protocols to keep tests hermetic and maintainable.

When to Use It

  • Starting a new Swift project that requires structured unit and UI tests
  • Adding tests for ViewModels, services, or utilities
  • Migrating from XCTest to Swift Testing or vice versa
  • Writing async tests that exercise end-to-end flows with mocked dependencies
  • Creating end-to-end UI tests (XCUITest) for login and user flows

Quick Start

  1. Step 1: Determine Testing Mode by checking CLAUDE.md
  2. Step 2: Identify what to test and choose the framework (Swift Testing or XCTest), then plan mocks
  3. Step 3: Write tests with the naming convention and generate mocks, then run tests locally

Best Practices

  • Test the right layers: ViewModels, Services/Repos, Formatters, and async flows
  • Avoid testing private implementation details or SwiftUI layout specifics
  • Inject mocks via protocols and prefer mocks that track interactions
  • Use the test_when_<Condition>_should_<Outcome>() naming convention
  • Keep unit tests separate from UI tests and don’t mix Swift Testing and XCTest in the same target

Example Use Cases

  • Test a LoginViewModel with valid credentials using a MockAuthService and verify isLoggedIn is true and error is nil
  • Test a failed login to ensure isLoggedIn is false and error is not nil
  • Generate a MockAuthService via a protocol and inject it into the ViewModel under test
  • Write an async test that awaits a login call and asserts outcomes
  • Add a UI test scenario using XCUITest to verify the login flow end-to-end

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers