---
description: Swift Testing
alwaysApply: false
---

# Swift Testing

Swift Testing framework, XCTest, and UI testing. Test behavior with expressive assertions.

## Swift Testing Framework

```swift
@Suite("UserService")
struct UserServiceTests {
    let sut: UserService
    let mockRepo: MockUserRepository

    init() {
        mockRepo = MockUserRepository()
        sut = UserService(repository: mockRepo)
    }

    @Test("creates user with valid input")
    func createUserValid() async throws {
        let user = try await sut.create(CreateUserRequest(name: "Alice", email: "a@test.com"))
        #expect(user.name == "Alice")
    }

    @Test("validates email format", arguments: ["invalid", "", "@missing"])
    func invalidEmails(email: String) async {
        await #expect(throws: ValidationError.invalidEmail) {
            try await sut.create(CreateUserRequest(name: "Test", email: email))
        }
    }
}
```

## Protocol-Based Mocking

```swift
protocol UserRepository {
    func findById(_ id: UUID) async throws -> User?
    func save(_ user: User) async throws -> User
}

final class MockUserRepository: UserRepository {
    var users: [UUID: User] = [:]
    func findById(_ id: UUID) async throws -> User? { users[id] }
    func save(_ user: User) async throws -> User { users[user.id] = user; return user }
}
```

## XCTest (Traditional)

```swift
func test_createOrder_withValidItems_succeeds() async throws {
    let order = try await sut.create(request)
    XCTAssertEqual(order.items.count, 1)
    XCTAssertEqual(order.status, .pending)
}
```

## Test Naming

- **XCTest:** `test_validate_emptyEmail_throwsValidationError()`
- **Swift Testing:** `@Test("validates email format rejects empty string")`

## Anti-Patterns

```swift
// Never: testing implementation details — test observable outcomes
// Never: shared mutable state between tests — fresh state per test
// Never: real network calls in tests — use protocol mocking
// Never: Task.sleep in tests — use expectations or test schedulers
```
