┌───────────────┐ │ 🧪 Testing │ ├───────────────┤ │ MockAPIClient │ │ ├─ projects │ │ ├─ chat │ │ └─ tasks │ ├───────────────┤ │ XCTest suite │ │ ✓✓✓✓✓ 5/5 │ └───────────────┘

Testing

MockAPIClient, unit tests, and test structure.

✓ ViewModel ✓ Decoding ✓ API Mock ✓ Flow E2E ✓ Edge Cases
Developer Docs Archon iOS · Swift · SwiftUI API v1

Testing

MockAPIClient, unit tests, and test structure.

Test Structure

All tests live in the ArchonTests/ target and use Apple's XCTest framework. No third-party test dependencies.

ArchonTests/
├── Mocks/
│   └── MockAPIClient.swift
├── Models/
│   ├── ArchonProjectTests.swift
│   ├── ArchonTaskTests.swift
│   ├── ChatMessageTests.swift
│   └── TaskEventTests.swift
├── ViewModels/
│   ├── BuilderViewModelTests.swift
│   ├── ProjectsViewModelTests.swift
│   └── SettingsViewModelTests.swift
├── Services/
│   ├── APIClientTests.swift
│   └── AuthManagerTests.swift
└── Resources/
    ├── mock-project.json
    ├── mock-task.json
    ├── mock-chat-message.json
    └── mock-task-event.json

MockAPIClient

The MockAPIClient conforms to APIClientProtocol and returns pre-configured responses without network calls:

class MockAPIClient: APIClientProtocol {
    // Configurable responses
    var projectsToReturn: [ArchonProject] = []
    var taskToReturn: ArchonTask?
    var chatMessagesToReturn: [ChatMessage] = []
    var profileToReturn: UserProfile?

    // Error injection
    var errorToThrow: APIError?
    var requestCount = 0
    var lastRequestMethod: HTTPMethod?
    var lastRequestPath: String?

    func request<T: Decodable>(
        method: HTTPMethod,
        path: String,
        body: (any Encodable)?
    ) async throws -> T {
        requestCount += 1
        lastRequestMethod = method
        lastRequestPath = path

        if let error = errorToThrow {
            throw error
        }

        // Return appropriate mock data based on path
        // ...
    }
}

Mock Usage Patterns

Happy Path
Configure mock to return valid data. Assert ViewModel state updates correctly.
Error Path
Set errorToThrow to an APIError. Assert the ViewModel shows the correct error state.
Request Verification
After calling ViewModel methods, check requestCount and lastRequestPath to verify the correct API calls were made.

What's Tested

📦
Model Decoding
All Codable models are tested against fixture JSON files to verify correct snake_case → camelCase mapping and date parsing.
🧠
ViewModel Logic
Business logic in ViewModels: loading states, error handling, data transformation, pagination, and search filtering.
🔌
API Client
Request construction, header injection, error mapping, retry behavior, and token refresh logic.
🔄
Flow Integration
End-to-end flows using MockAPIClient: sign in → load projects → select project → view tasks.

Test Example

class ProjectsViewModelTests: XCTestCase {
    var mockClient: MockAPIClient!
    var viewModel: ProjectsViewModel!

    override func setUp() {
        mockClient = MockAPIClient()
        viewModel = ProjectsViewModel(apiClient: mockClient)
    }

    func testLoadProjects() async {
        // Given
        mockClient.projectsToReturn = [
            ArchonProject(id: UUID(), name: "Test", ...)
        ]

        // When
        await viewModel.loadProjects()

        // Then
        XCTAssertEqual(viewModel.projects.count, 1)
        XCTAssertEqual(viewModel.projects.first?.name, "Test")
        XCTAssertFalse(viewModel.isLoading)
    }

    func testLoadProjectsError() async {
        // Given
        mockClient.errorToThrow = .networkError(...)

        // When
        await viewModel.loadProjects()

        // Then
        XCTAssertNotNil(viewModel.errorMessage)
        XCTAssertTrue(viewModel.projects.isEmpty)
    }
}

Running Tests

# Via Xcode
Cmd + U

# Via command line
xcodebuild test \
  -project Archon.xcodeproj \
  -scheme Archon \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  | xcpretty

Test Fixtures

JSON fixture files in ArchonTests/Resources/ represent realistic API responses. When the API schema changes, update these fixtures first, then fix any decoding failures.