iOS Testing Best Practices
Comprehensive testing guide for iOS and Swift applications, written at principal engineer level. Contains 44 rules across 8 categories, prioritized by impact to guide test architecture decisions, test authoring patterns, and CI infrastructure.
Clinic Architecture Contract (iOS 26 / Swift 6.2)
All guidance in this skill assumes the clinic modular MVVM-C architecture:
- Feature modules import Domain
- DesignSystem only (never Data , never sibling features)
-
App target is the convergence point and owns DependencyContainer , concrete coordinators, and Route Shell wiring
-
Domain stays pure Swift and defines models plus repository, *Coordinating , ErrorRouting , and AppError contracts
-
Data owns SwiftData/network/sync/retry/background I/O and implements Domain protocols
-
Read/write flow defaults to stale-while-revalidate reads and optimistic queued writes
-
ViewModels call repository protocols directly (no default use-case/interactor layer)
When to Apply
Reference these guidelines when:
-
Writing new unit tests or UI tests for iOS apps
-
Designing testable architecture with dependency injection
-
Testing async/await, actors, and Combine publishers
-
Setting up snapshot testing or visual regression suites
-
Configuring CI pipelines, test plans, and parallel execution
Rule Categories by Priority
Priority Category Impact Prefix
1 Test Architecture & Testability CRITICAL arch-
2 Unit Testing Fundamentals CRITICAL unit-
3 Test Doubles & Isolation HIGH mock-
4 Async & Concurrency Testing HIGH async-
5 SwiftUI Testing MEDIUM-HIGH swiftui-
6 UI & Acceptance Testing MEDIUM ui-
7 Snapshot & Visual Testing MEDIUM snap-
8 Test Reliability & CI LOW-MEDIUM ci-
Quick Reference
- Test Architecture & Testability (CRITICAL)
-
arch-protocol-dependencies
-
Depend on protocols, not concrete types
-
arch-constructor-injection
-
Use constructor injection over service locators
-
arch-test-target-separation
-
Separate unit and UI test targets
-
arch-testable-import
-
Use @testable import sparingly
-
arch-single-responsibility-tests
-
One assertion concept per test
-
arch-arrange-act-assert
-
Structure tests as Arrange-Act-Assert
- Unit Testing Fundamentals (CRITICAL)
-
unit-swift-testing-framework
-
Use Swift Testing over XCTest for new tests
-
unit-parameterized-tests
-
Use parameterized tests for input variations
-
unit-descriptive-test-names
-
Name tests after the behavior they verify
-
unit-expect-over-assert
-
Use #expect and #require over XCTAssert
-
unit-require-preconditions
-
Use #require for test preconditions
-
unit-test-suites
-
Organize related tests into suites
-
unit-test-tags
-
Use tags to categorize cross-cutting tests
- Test Doubles & Isolation (HIGH)
-
mock-protocol-based-mocks
-
Create mocks from protocols, not subclasses
-
mock-spy-for-verification
-
Use spies to verify interactions
-
mock-stub-return-values
-
Use stubs for deterministic return values
-
mock-avoid-over-mocking
-
Avoid mocking value types and simple logic
-
mock-fake-for-integration
-
Use in-memory fakes for integration tests
-
mock-dependency-container
-
Use a dependency container for test configuration
- Async & Concurrency Testing (HIGH)
-
async-await-directly
-
Await async functions directly in tests
-
async-confirmation
-
Use confirmation() for callback-based APIs
-
async-mainactor-isolation
-
Test MainActor-isolated code on MainActor
-
async-actor-testing
-
Test actor state through async interface
-
async-task-cancellation
-
Test task cancellation paths explicitly
- SwiftUI Testing (MEDIUM-HIGH)
-
swiftui-test-observable-models
-
Test @Observable models as plain objects
-
swiftui-environment-injection
-
Inject environment dependencies for tests
-
swiftui-preview-as-test
-
Use previews as visual smoke tests
-
swiftui-view-model-extraction
-
Extract logic from views into testable models
-
swiftui-binding-testing
-
Test binding behavior with @Bindable
- UI & Acceptance Testing (MEDIUM)
-
ui-accessibility-identifiers
-
Use accessibility identifiers for element queries
-
ui-page-object-pattern
-
Encapsulate screens in page objects
-
ui-launch-arguments
-
Configure test state via launch arguments
-
ui-wait-for-elements
-
Wait for elements instead of using sleep()
-
ui-test-user-journeys
-
Test complete user journeys, not individual screens
-
ui-reset-state-between-tests
-
Reset app state between UI tests
- Snapshot & Visual Testing (MEDIUM)
-
snap-swift-snapshot-testing
-
Use swift-snapshot-testing for visual regression
-
snap-device-matrix
-
Snapshot across device sizes and traits
-
snap-named-references
-
Use named snapshot references for clarity
-
snap-inline-snapshots
-
Use inline snapshots for non-image assertions
- Test Reliability & CI (LOW-MEDIUM)
-
ci-test-plans
-
Use Xcode Test Plans for environment configurations
-
ci-parallel-execution
-
Enable parallel test execution
-
ci-flaky-test-quarantine
-
Quarantine flaky tests instead of disabling them
-
ci-deterministic-test-data
-
Use deterministic test data over random generation
-
ci-coverage-thresholds
-
Set coverage thresholds for critical paths
How to Use
Read individual reference files for detailed explanations and code examples:
-
Section definitions - Category structure and impact levels
-
Rule template - Template for adding new rules
Reference Files
File Description
references/_sections.md Category definitions and ordering
assets/templates/_template.md Template for new rules
metadata.json Version and reference information