一键导入
testing
Testing patterns and conventions. Use when writing unit tests, using Swift Testing framework, or following Given/When/Then structure.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Testing patterns and conventions. Use when writing unit tests, using Swift Testing framework, or following Given/When/Then structure.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Swift 6 concurrency patterns. Use when working with async/await, actors, MainActor isolation, or Sendable conformance.
Creates Features for dependency injection. Use when creating features, exposing public entry points, or wiring up dependencies.
Creates Repositories that abstract data access. Use when creating repositories, transforming DTOs to domain models, or implementing local-first caching. Supports remote-only, local-only, and cached (remote + local) repositories with CachePolicy.
Creates a new feature module with minimal viable structure. Use when bootstrapping a new feature from scratch, scaffolding the Tuist module, Container, Feature entry point, DeepLinkHandler, and initial screen with placeholder Text view. Includes all unit tests, mocks, stubs, and app integration. For adding domain/data layers afterward, use /datasource, /repository, /usecase. For enhancing views, use /view, /viewmodel, /navigator.
Creates Navigator for navigation. Use when setting up navigation, adding navigation to ViewModels, or testing navigation behavior.
Creates ViewModels with state management. Use when creating ViewModels, implementing ViewState pattern, or adding state management for features. Delegates to /usecase for domain use cases and to /feature for Container/Feature wiring.
| name | testing |
| description | Testing patterns and conventions. Use when writing unit tests, using Swift Testing framework, or following Given/When/Then structure. |
Guide for writing tests using Swift Testing framework following project conventions.
| Framework | Usage |
|---|---|
| Testing (Swift Testing) | Unit tests, integration tests |
| ChallengeSnapshotTestKit | Snapshot tests for UI components (see /snapshot skill) |
| XCTest | UI tests (see /ui-tests skill) |
| Include | Exclude |
|---|---|
Source targets (Sources/) | Mock targets (Mocks/) |
| Production code | Test targets (Tests/) |
| External libraries |
Always name the object being tested as sut:
// RIGHT
let sut = GetUserUseCase(client: mockClient)
// WRONG
let useCase = GetUserUseCase(client: mockClient)
All tests MUST include a description in the @Test attribute:
// RIGHT
@Test("Fetches user successfully from repository")
func fetchesUserSuccessfully() async throws { }
// WRONG - Missing description
@Test
func fetchesUserSuccessfully() async throws { }
All tests must use // Given, // When, // Then comments:
@Test("Fetches user successfully from repository")
func fetchesUserSuccessfully() async throws {
// Given
let expectedUser = User(id: 1, name: "John")
let mockClient = HTTPClientMock()
mockClient.result = .success(expectedUser.encoded())
let sut = GetUserUseCase(client: mockClient)
// When
let result = try await sut.execute(userId: 1)
// Then
#expect(result == expectedUser)
}
// Use #expect for assertions
#expect(value == expected)
#expect(array.isEmpty)
#expect(count > 0)
// Use #require for unwrapping (fails test if nil)
let data = try #require(response.data)
let user = try #require(users.first)
// Use #expect(throws:) for error testing
await #expect(throws: HTTPError.invalidURL) {
try await client.request(invalidEndpoint)
}
Always compare full objects instead of checking individual properties:
// RIGHT - Compare full objects using stubs
let expected = Character.stub()
let value = try await sut.getCharacter(id: 1)
#expect(value == expected)
// WRONG - Checking individual properties
#expect(result.id == 1)
#expect(result.name == "Rick Sanchez")
Rules:
value as the variable name for the result being testedexpected variable with the stub matching the expected output#expect(value == expected)Always prefer @Test(arguments:) for testing multiple cases:
@Test("Endpoint supports HTTP method", arguments: [
HTTPMethod.get,
HTTPMethod.post,
HTTPMethod.put,
])
func endpointSupportsHTTPMethod(_ method: HTTPMethod) {
// Given
let path = "/test"
// When
let sut = Endpoint(path: path, method: method)
// Then
#expect(sut.method == method)
}
For ViewModel actions with multiple outcomes (success/failure/edge cases), use scenario structs with @Test(arguments:). Each scenario defines its Given inputs and Expected outputs:
@Test("didAppear produces expected outcome per scenario", arguments: DidAppearScenario.all)
func didAppear(scenario: DidAppearScenario) async {
// Given
getUseCaseMock.result = scenario.given.result
// When
await sut.didAppear()
// Then
#expect(sut.state == scenario.expected.state)
#expect(trackerMock.loadErrorDescriptions == scenario.expected.loadErrorDescriptions)
}
See references/test-patterns.md for scenario struct pattern and helper methods.
// RIGHT - Descriptive function name, no "test" prefix
@Test("Returns correct value when input is valid")
func returnsCorrectValue() { }
// WRONG - "test" prefix
@Test("Returns correct value")
func testReturnsCorrectValue() { }
Organize tests by method name using // MARK: - sections:
// MARK: - Initial State
// MARK: - didAppear
// MARK: - didTapOnRetryButton
// MARK: - didPullToRefresh
// MARK: - didTapOnEpisodes
// MARK: - Helpers
Each test verifies all side effects of an action together (state, navigation, tracking) — do not split into separate tests:
// RIGHT — One test per action verifying all side effects
@Test("didTapOnCharacterButton navigates to characters and tracks event")
func didTapOnCharacterButton() {
// When
sut.didTapOnCharacterButton()
// Then
#expect(navigatorMock.navigateToCharactersCallCount == 1)
#expect(trackerMock.characterButtonTappedCallCount == 1)
}
// WRONG — Separate tests for each side effect of the same action
@Test("didTapOnCharacterButton navigates to characters")
func didTapOnCharacterButtonNavigates() { ... }
@Test("didTapOnCharacterButton tracks event")
func didTapOnCharacterButtonTracks() { ... }
Use @Suite(.timeLimit(.minutes(1))) only for test suites that use async/await:
// Async tests need time limit
@Suite(.timeLimit(.minutes(1)))
struct GetCharacterUseCaseTests { }
// Synchronous tests don't need time limit
struct CharacterStatusTests { }
Tests/
├── Unit/
│ ├── Domain/UseCases/{Name}UseCaseTests.swift
│ ├── Data/Repositories/{Name}RepositoryTests.swift
│ ├── Presentation/{Screen}/ViewModels/{Screen}ViewModelTests.swift
│ └── Feature/{Feature}FeatureTests.swift
├── Snapshots/Presentation/{Screen}/{Screen}ViewSnapshotTests.swift
└── Shared/
├── Stubs/{Name}+Stub.swift
├── Mocks/{Name}Mock.swift
├── Fixtures/{name}.json
├── Extensions/{Name}+Equatable.swift
└── Resources/test-avatar.jpg
{ComponentName}Tests.swift in Tests/Unit/@Test attributes include a descriptionsuttest prefix in method namesTests/Shared/Stubs/Tests/Shared/Mocks/ or Mocks/)Tests/Shared/Extensions/ for types with ErrorTests/Shared/Fixtures/ (Mapper and DataSource tests only)Tests/Shared/Resources/