---
name: swift-testing-patterns
description: Unit testing, Mocking, UI testing patterns cho Swift Enterprise (XCTest, async testing). Use for ViewModels testing, mocking services, hoặc testing best practices.
compatibility: "Swift 5.9+, XCTest, Swift Testing"
metadata:
  author: Velik Ho
  version: "1.0"
---

# Swift Testing Patterns

Unit và Integration testing patterns cho Swift Enterprise.

## When to Use This Skill / Khi Nào Sử Dụng

Use this skill when:
- Writing unit tests / Viết unit tests
- Mocking services / Mock services
- Testing ViewModels / Test ViewModels
- Async code testing / Test async code

## Core Patterns / Mẫu Chính

### Mock Service

```swift
// Tests/Mocks/MockAPIService.swift

final class MockAPIService: APIServiceProtocol {
    
    var mockResult: Any?
    var mockError: Error?
    var requestCalled = false
    
    func request<T: Decodable>(
        endpoint: String,
        method: HTTPMethod,
        body: Encodable?,
        headers: [String: String]?
    ) async throws -> T {
        requestCalled = true
        
        if let error = mockError {
            throw error
        }
        
        guard let result = mockResult as? T else {
            throw APIError.unknown
        }
        
        return result
    }
}
```

### ViewModel Testing

```swift
// Tests/ViewModelTests/HomeViewModelTests.swift

import XCTest
@testable import MyApp

@MainActor
final class HomeViewModelTests: XCTestCase {
    
    var sut: HomeViewModel!
    var mockService: MockAPIService!
    
    override func setUp() {
        super.setUp()
        mockService = MockAPIService()
        sut = HomeViewModel(apiService: mockService)
    }
    
    override func tearDown() {
        sut = nil
        mockService = nil
        super.tearDown()
    }
    
    func test_loadItems_success() async {
        // Arrange
        let expectedItems = [Item(id: "1", name: "Test")]
        mockService.mockResult = expectedItems
        
        // Act
        await sut.loadItems()
        
        // Assert
        XCTAssertEqual(sut.items.count, 1)
        XCTAssertEqual(sut.items.first?.name, "Test")
        XCTAssertFalse(sut.isLoading)
        XCTAssertNil(sut.errorMessage)
    }
    
    func test_loadItems_failure() async {
        // Arrange
        mockService.mockError = APIError.networkError(NSError(domain: "", code: -1))
        
        // Act
        await sut.loadItems()
        
        // Assert
        XCTAssertTrue(sut.items.isEmpty)
        XCTAssertNotNil(sut.errorMessage)
    }
}
```

### Async Testing

```swift
func test_asyncOperation() async throws {
    // Use async/await directly
    let result = try await sut.performAsync()
    XCTAssertTrue(result)
}

// With expectation (legacy)
func test_asyncWithExpectation() {
    let expectation = expectation(description: "Async complete")
    
    Task {
        await sut.loadData()
        expectation.fulfill()
    }
    
    wait(for: [expectation], timeout: 5.0)
    XCTAssertFalse(sut.items.isEmpty)
}
```

### Published Property Testing

```swift
import Combine

func test_publishedProperty() {
    var cancellables = Set<AnyCancellable>()
    let expectation = expectation(description: "Value changed")
    
    sut.$items
        .dropFirst() // Skip initial value
        .sink { items in
            XCTAssertEqual(items.count, 1)
            expectation.fulfill()
        }
        .store(in: &cancellables)
    
    Task {
        await sut.loadItems()
    }
    
    wait(for: [expectation], timeout: 5.0)
}
```

## Quick Reference / Tham Chiếu Nhanh

| Pattern | Usage |
|---------|-------|
| Mock Service | Protocol + mock implementation |
| `@MainActor` | Required for ViewModel tests |
| Arrange-Act-Assert | Standard test structure |
| `async throws` | Direct async testing |

## Resources / Tài Nguyên

- [Swift Enterprise Architect](../swift-enterprise-architect/SKILL.md)
- [Apple XCTest](https://developer.apple.com/documentation/xctest)
