Testing Strategy & Mocks
Relevant source files
The following files were used as context for generating this wiki page:
- README.md
- CLAUDE.md
- TrackurrTests/Persistence/Stores/CategoryStoreTests.swift
- Features/TagsFeature/Tests/TagsFeatureTests/Views/TagList/TagListViewModelTests.swift
- Features/CategoriesFeature/Tests/CategoriesFeatureTests/Views/CategoryForm/CategoryFormViewModelTests.swift
- TrackurrTests/Persistence/Seeding/DataSeederTests.swift
- Packages/Services/Tests/ServicesTests/SubscriptionAnalyticsImplTests.swift
The testing strategy in Trackurr is built upon Apple's Swift Testing framework, utilizing a modular approach that separates unit tests for services, persistence stores, and view models. The architecture leverages Dependency Injection (DI) via the Factory library, allowing for seamless replacement of live services with test doubles (mocks) during test execution.
Sources: README.md:58-65, CLAUDE.md:54-57
Core Testing Principles
The project adheres to a structured testing methodology focusing on isolation and reliability:
- Framework: Use of
import Testingand@Testmacros for modern, concurrent testing. - Locality: Tests are kept near the owning module (e.g.,
Features/<FeatureName>/Tests/) or in theTrackurrTests/directory for app-target coverage. - Isolation: Dependencies are injected through a centralized Factory container, which is reconfigured in test setup to provide mock implementations.
- Persistence: SwiftData persistence is tested using in-memory model containers to ensure no side effects between test runs.
Sources: CLAUDE.md:54-57, README.md:144-150, TrackurrTests/Persistence/Stores/CategoryStoreTests.swift:15-19
Testing Infrastructure Flow
The following diagram illustrates how the test environment is configured using Factory to swap real implementations with mocks.
Test Categories
Unit Testing (ViewModels)
ViewModel tests verify state changes, input validation, and analytics logging. They use mocks for stores and services to simulate data persistence and business logic outcomes.
| Test Focus | Description | Example File |
|---|---|---|
| State Management | Ensures UI state (e.g., isLoading, errorMessage) updates correctly. | TagListViewModelTests.swift |
| Validation Logic | Checks constraints like character limits or non-empty fields. | CategoryFormViewModelTests.swift |
| Analytics | Verifies that correct events are logged during user interactions. | TagFormViewModelTests.swift |
Sources: Features/CategoriesFeature/Tests/CategoriesFeatureTests/Views/CategoryForm/CategoryFormViewModelTests.swift:85-177, Features/TagsFeature/Tests/TagsFeatureTests/Views/TagList/TagListViewModelTests.swift:39-47
Persistence Testing (Stores)
Persistence tests ensure that SwiftData entities are correctly mapped from domain models and that CRUD operations (Create, Read, Update, Delete) function as expected within an in-memory context.
private func makeSUT() throws -> (store: CategoryStoreImpl, context: ModelContext) {
let context = ModelContext.inMemory()
let store = CategoryStoreImpl(context: context)
return (store, context)
}
Sources: TrackurrTests/Persistence/Stores/CategoryStoreTests.swift:15-19
Service & Logic Testing
Tests for services like SubscriptionAnalyticsImpl verify complex business calculations, such as monthly totals across multiple currencies and upcoming renewal dates. These tests often mock the DateProvider to ensure deterministic results regardless of when the test is run.
Sources: Packages/Services/Tests/ServicesTests/SubscriptionAnalyticsImplTests.swift:31-70
Mocking Strategy
Mocks are centralized in the Packages/Mocks package. They generally follow a pattern of tracking call counts and providing configurable return values or error triggers.
Common Mock Patterns
- Call Counters: Variables like
fetchAllCallCountincremented on every invocation. - Result Injection: Properties like
categoriesToReturnallow tests to define what the mock should provide. - Error Simulation:
shouldThrowErrorflags used to test failure paths in the application.
Data Seeding for Testing
Trackurr includes a robust data seeding mechanism tested in DataSeederTests. This ensures that system categories, payment methods, and subscription providers are correctly initialized on first launch or when forced.
Seeding Verification Table:
| Component | Test Case | Logic Verified |
|---|---|---|
| System Categories | seedIfNeededSeedsCategories | Correct count (12) and prefix (category.) |
| Payment Methods | seedIfNeededSeedsPaymentMethods | Correct count (6) and prefix (paymentMethod.) |
| Deduplication | forceSeedDeduplicates | Ensures no duplicates if seeding runs multiple times. |
| Version Tracking | seedIfNeededUpdatesVersionStore | Versions are stored in UserDefaults to prevent re-seeding. |
Sources: TrackurrTests/Persistence/Seeding/DataSeederTests.swift:75-120, TrackurrTests/Persistence/Seeding/DataSeederTests.swift:165-176
Summary
The Testing Strategy & Mocks within Trackurr provide a comprehensive safety net through isolated unit tests and reliable persistence validation. By utilizing Factory for DI and in-memory SwiftData contexts, the project maintains a fast and deterministic test suite, critical for validating complex features like multi-currency analytics and automated data seeding.
Sources: README.md:144-150, CLAUDE.md:54-57