✦
┌───────────────┐
│ 🧪 Testing │
├───────────────┤
│ MockAPIClient │
│ ├─ projects │
│ ├─ chat │
│ └─ tasks │
├───────────────┤
│ XCTest suite │
│ ✓✓✓✓✓ 5/5 │
└───────────────┘
Testing
MockAPIClient, unit tests, and test structure.
✓ ViewModel
✓ Decoding
✓ API Mock
✓ Flow E2E
✓ Edge Cases
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.