| name | swift-testing |
| description | Write, review, or migrate unit tests using Swift Testing (@Test, @Suite, #expect, #require). Use when writing new tests, converting existing Quick+Nimble specs to Swift Testing, reviewing test quality, debugging flaky tests, or improving test structure in this iOS template project. Triggers on: "write test", "add test", "test this", "migrate test", "convert spec", "Swift Testing".
|
Swift Testing
Overview
Write, review, migrate, and debug tests using Swift Testing for this iOS template project. This project uses Quick + Nimble (*Spec.swift) as the legacy framework and Swift Testing (*Tests.swift) for all new tests.
Agent Behavior Contract
- Swift Testing for all new unit/integration tests. Retain Quick+Nimble only for existing specs; do not migrate unless asked.
- Use
#expect as the default assertion; use #require only when subsequent lines depend on a prerequisite value.
- Design tests to run safely in parallel by default. Fix shared state before reaching for
.serialized.
- Use inline
private struct / private actor stubs conforming to domain protocols — no generated mocks.
- Use
actor stubs when the protocol requires async or has mutable state; use struct stubs otherwise.
- Prefer parameterized tests (
@Test(arguments:)) over duplicated test methods.
- Use traits (
.enabled, .disabled, .timeLimit, .bug, .tags) instead of ad-hoc comments.
- Import
Testing only in test targets, never in app/library targets.
- Keep tests deterministic — no real network calls. Use
NetworkStubber (OHHTTPStubs) for network-level tests.
Quick Triage
Before writing or reviewing tests, clarify:
- New or migration? Writing fresh tests vs converting an existing
*Spec.swift.
- Which module? App (
{PROJECT_NAME}Tests/), Data (Modules/Data/Tests/), Domain (Modules/Domain/Tests/), or Model (Modules/Model/Tests/).
- Async? Does the code under test use
async/await?
- Shared resources? Database, network, file system, or singleton state involved?
File Naming & Placement
| Framework | Suffix | Placement |
|---|
| Swift Testing | *Tests.swift | Tests/Sources/ mirroring production path (e.g. Repositories/, Entities/) |
| Quick + Nimble | *Spec.swift | Tests/Sources/Specs/ |
Canonical Pattern (this project)
import Testing
@testable import Data
import Domain
@Suite("DefaultRemoteConfigRepository")
struct DefaultRemoteConfigRepositoryTests {
@Test("returns a typed stored value")
func returnsATypedStoredValue() async {
let booleanKey = RemoteConfigKey(name: "feature_enabled", defaultValue: false)
let repository = DefaultRemoteConfigRepository(
source: StubRemoteConfigSource(values: [
booleanKey.name: .string("true")
])
)
let value = await repository.value(for: booleanKey)
#expect(value == true)
}
}
private actor StubRemoteConfigSource: RemoteConfigSource {
private let values: [String: RemoteConfigStoredValue]
init(values: [String: RemoteConfigStoredValue] = [:]) {
self.values = values
}
func refresh() async throws {}
func value(forKey key: String) async -> RemoteConfigStoredValue? {
values[key]
}
}
Key observations:
struct suite, not class
@Suite("DisplayName") with human-readable name matching the type under test
@Test("description") with lowercase sentence describing behavior
- Function name is camelCase matching the description (no
test prefix)
- Arrange / Act / Assert with blank line separations
- Inline
private actor stub at bottom of file (actor because protocol has async)
@testable import for the module under test; regular import for its dependencies
Routing to References
Load the appropriate reference file based on the task:
| Task | Reference |
|---|
| Writing new tests, suite structure, naming | references/fundamentals.md |
| Assertions, error checking, known issues | references/expectations.md |
| Traits, tags, conditional execution | references/traits-and-tags.md |
| Data-driven / parameterized tests | references/parameterized-testing.md |
| Async/await, callbacks, confirmations | references/async-testing.md |
| Creating stubs, fakes, network mocks | references/test-doubles.md |
Verification Checklist
Before finishing, confirm: