swift-code-reviewer-skill 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +43 -169
  2. package/README.md +43 -2
  3. package/SKILL.md +194 -711
  4. package/bin/install.js +1 -1
  5. package/package.json +2 -1
  6. package/references/agent-loop-feedback.md +148 -0
  7. package/references/companion-skills.md +70 -0
  8. package/references/spec-adherence.md +157 -0
  9. package/skills/README.md +43 -0
  10. package/skills/swift-concurrency/NOTICE.md +18 -0
  11. package/skills/swift-concurrency/SKILL.md +235 -0
  12. package/skills/swift-concurrency/references/actors.md +640 -0
  13. package/skills/swift-concurrency/references/async-await-basics.md +249 -0
  14. package/skills/swift-concurrency/references/async-sequences.md +635 -0
  15. package/skills/swift-concurrency/references/core-data.md +533 -0
  16. package/skills/swift-concurrency/references/glossary.md +96 -0
  17. package/skills/swift-concurrency/references/linting.md +38 -0
  18. package/skills/swift-concurrency/references/memory-management.md +542 -0
  19. package/skills/swift-concurrency/references/migration.md +721 -0
  20. package/skills/swift-concurrency/references/performance.md +574 -0
  21. package/skills/swift-concurrency/references/sendable.md +578 -0
  22. package/skills/swift-concurrency/references/tasks.md +604 -0
  23. package/skills/swift-concurrency/references/testing.md +565 -0
  24. package/skills/swift-concurrency/references/threading.md +452 -0
  25. package/skills/swift-expert/NOTICE.md +18 -0
  26. package/skills/swift-expert/SKILL.md +226 -0
  27. package/skills/swift-expert/references/async-concurrency.md +363 -0
  28. package/skills/swift-expert/references/memory-performance.md +380 -0
  29. package/skills/swift-expert/references/protocol-oriented.md +357 -0
  30. package/skills/swift-expert/references/swiftui-patterns.md +294 -0
  31. package/skills/swift-expert/references/testing-patterns.md +402 -0
  32. package/skills/swift-testing/NOTICE.md +18 -0
  33. package/skills/swift-testing/SKILL.md +295 -0
  34. package/skills/swift-testing/references/async-testing.md +245 -0
  35. package/skills/swift-testing/references/dump-snapshot-testing.md +265 -0
  36. package/skills/swift-testing/references/fixtures.md +193 -0
  37. package/skills/swift-testing/references/integration-testing.md +189 -0
  38. package/skills/swift-testing/references/migration-xctest.md +301 -0
  39. package/skills/swift-testing/references/parameterized-tests.md +171 -0
  40. package/skills/swift-testing/references/snapshot-testing.md +201 -0
  41. package/skills/swift-testing/references/test-doubles.md +243 -0
  42. package/skills/swift-testing/references/test-organization.md +231 -0
  43. package/skills/swiftui-expert-skill/NOTICE.md +18 -0
  44. package/skills/swiftui-expert-skill/SKILL.md +281 -0
  45. package/skills/swiftui-expert-skill/references/accessibility-patterns.md +151 -0
  46. package/skills/swiftui-expert-skill/references/animation-advanced.md +403 -0
  47. package/skills/swiftui-expert-skill/references/animation-basics.md +284 -0
  48. package/skills/swiftui-expert-skill/references/animation-transitions.md +326 -0
  49. package/skills/swiftui-expert-skill/references/charts-accessibility.md +135 -0
  50. package/skills/swiftui-expert-skill/references/charts.md +602 -0
  51. package/skills/swiftui-expert-skill/references/image-optimization.md +203 -0
  52. package/skills/swiftui-expert-skill/references/latest-apis.md +464 -0
  53. package/skills/swiftui-expert-skill/references/layout-best-practices.md +266 -0
  54. package/skills/swiftui-expert-skill/references/liquid-glass.md +414 -0
  55. package/skills/swiftui-expert-skill/references/list-patterns.md +394 -0
  56. package/skills/swiftui-expert-skill/references/macos-scenes.md +318 -0
  57. package/skills/swiftui-expert-skill/references/macos-views.md +357 -0
  58. package/skills/swiftui-expert-skill/references/macos-window-styling.md +303 -0
  59. package/skills/swiftui-expert-skill/references/performance-patterns.md +403 -0
  60. package/skills/swiftui-expert-skill/references/scroll-patterns.md +293 -0
  61. package/skills/swiftui-expert-skill/references/sheet-navigation-patterns.md +363 -0
  62. package/skills/swiftui-expert-skill/references/state-management.md +417 -0
  63. package/skills/swiftui-expert-skill/references/view-structure.md +389 -0
  64. package/skills/swiftui-ui-patterns/NOTICE.md +18 -0
  65. package/skills/swiftui-ui-patterns/SKILL.md +95 -0
  66. package/skills/swiftui-ui-patterns/references/app-wiring.md +201 -0
  67. package/skills/swiftui-ui-patterns/references/async-state.md +96 -0
  68. package/skills/swiftui-ui-patterns/references/components-index.md +50 -0
  69. package/skills/swiftui-ui-patterns/references/controls.md +57 -0
  70. package/skills/swiftui-ui-patterns/references/deeplinks.md +66 -0
  71. package/skills/swiftui-ui-patterns/references/focus.md +90 -0
  72. package/skills/swiftui-ui-patterns/references/form.md +97 -0
  73. package/skills/swiftui-ui-patterns/references/grids.md +71 -0
  74. package/skills/swiftui-ui-patterns/references/haptics.md +71 -0
  75. package/skills/swiftui-ui-patterns/references/input-toolbar.md +51 -0
  76. package/skills/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
  77. package/skills/swiftui-ui-patterns/references/list.md +86 -0
  78. package/skills/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
  79. package/skills/swiftui-ui-patterns/references/macos-settings.md +71 -0
  80. package/skills/swiftui-ui-patterns/references/matched-transitions.md +59 -0
  81. package/skills/swiftui-ui-patterns/references/media.md +73 -0
  82. package/skills/swiftui-ui-patterns/references/menu-bar.md +101 -0
  83. package/skills/swiftui-ui-patterns/references/navigationstack.md +159 -0
  84. package/skills/swiftui-ui-patterns/references/overlay.md +45 -0
  85. package/skills/swiftui-ui-patterns/references/performance.md +62 -0
  86. package/skills/swiftui-ui-patterns/references/previews.md +48 -0
  87. package/skills/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
  88. package/skills/swiftui-ui-patterns/references/scrollview.md +87 -0
  89. package/skills/swiftui-ui-patterns/references/searchable.md +71 -0
  90. package/skills/swiftui-ui-patterns/references/sheets.md +155 -0
  91. package/skills/swiftui-ui-patterns/references/split-views.md +72 -0
  92. package/skills/swiftui-ui-patterns/references/tabview.md +114 -0
  93. package/skills/swiftui-ui-patterns/references/theming.md +71 -0
  94. package/skills/swiftui-ui-patterns/references/title-menus.md +93 -0
  95. package/skills/swiftui-ui-patterns/references/top-bar.md +49 -0
@@ -0,0 +1,295 @@
1
+ ---
2
+ name: swift-testing
3
+ description: 'Expert guidance on Swift Testing best practices, patterns, and implementation. Use when developers mention: (1) Swift Testing, @Test, #expect, #require, or @Suite, (2) "use Swift Testing" or "modern testing patterns", (3) test doubles, mocks, stubs, spies, or fixtures, (4) unit tests, integration tests, or snapshot tests, (5) migrating from XCTest to Swift Testing, (6) TDD, Arrange-Act-Assert, or F.I.R.S.T. principles, (7) parameterized tests or test organization.'
4
+ ---
5
+ # Swift Testing
6
+
7
+ ## Overview
8
+
9
+ This skill provides expert guidance on Swift Testing, covering the modern Swift Testing framework, test doubles (mocks, stubs, spies), fixtures, integration testing, snapshot testing, and migration from XCTest. Use this skill to help developers write reliable, maintainable tests following F.I.R.S.T. principles and Arrange-Act-Assert patterns.
10
+
11
+ ## Agent Behavior Contract (Follow These Rules)
12
+
13
+ 1. Use Swift Testing framework (`@Test`, `#expect`, `#require`, `@Suite`) for all new tests, not XCTest.
14
+ 2. Always structure tests with clear Arrange-Act-Assert phases.
15
+ 3. Follow F.I.R.S.T. principles: Fast, Isolated, Repeatable, Self-Validating, Timely.
16
+ 4. Use proper test double terminology per Martin Fowler's taxonomy (Dummy, Fake, Stub, Spy, SpyingStub, Mock).
17
+ 5. Place fixtures close to models with `#if DEBUG`, not in test targets.
18
+ 6. Place test doubles close to interfaces with `#if DEBUG`, not in test targets.
19
+ 7. Prefer state verification over behavior verification - simpler, less brittle tests.
20
+ 8. Use `#expect` for soft assertions (continue on failure) and `#require` for hard assertions (stop on failure).
21
+
22
+ ## Quick Decision Tree
23
+
24
+ When a developer needs testing guidance, follow this decision tree:
25
+
26
+ 1. **Starting fresh with Swift Testing?**
27
+ - Read `references/test-organization.md` for suites, tags, traits
28
+ - Read `references/async-testing.md` for async test patterns
29
+
30
+ 2. **Need to create test data?**
31
+ - Read `references/fixtures.md` for fixture patterns and placement
32
+ - Read `references/test-doubles.md` for mock/stub/spy patterns
33
+
34
+ 3. **Testing multiple inputs?**
35
+ - Read `references/parameterized-tests.md` for parameterized testing
36
+
37
+ 4. **Testing module interactions?**
38
+ - Read `references/integration-testing.md` for integration test patterns
39
+
40
+ 5. **Testing UI for regressions?**
41
+ - Read `references/snapshot-testing.md` for snapshot testing setup
42
+
43
+ 6. **Testing data structures or state?**
44
+ - Read `references/dump-snapshot-testing.md` for text-based snapshot testing
45
+
46
+ 7. **Migrating from XCTest?**
47
+ - Read `references/migration-xctest.md` for migration guide
48
+
49
+ ## Triage-First Playbook (Common Errors -> Next Best Move)
50
+
51
+ - "XCTAssertEqual is unavailable" / need to modernize tests
52
+ - Use `references/migration-xctest.md` for XCTest to Swift Testing migration
53
+ - Need to test async code
54
+ - Use `references/async-testing.md` for async patterns, confirmation, timeouts
55
+ - Tests are slow or flaky
56
+ - Check F.I.R.S.T. principles, use proper mocking per `references/test-doubles.md`
57
+ - Need deterministic test data
58
+ - Use `references/fixtures.md` for fixture patterns with fixed dates
59
+ - Need to test multiple scenarios efficiently
60
+ - Use `references/parameterized-tests.md` for parameterized testing
61
+ - Need to verify component interactions
62
+ - Use `references/integration-testing.md` for integration test patterns
63
+
64
+ ## Core Syntax
65
+
66
+ ### Basic Test
67
+
68
+ ```swift
69
+ import Testing
70
+
71
+ @Test func basicTest() {
72
+ #expect(1 + 1 == 2)
73
+ }
74
+ ```
75
+
76
+ ### Test with Description
77
+
78
+ ```swift
79
+ @Test("Adding items increases cart count")
80
+ func addItem() {
81
+ let cart = Cart()
82
+ cart.add(item)
83
+ #expect(cart.count == 1)
84
+ }
85
+ ```
86
+
87
+ ### Async Test
88
+
89
+ ```swift
90
+ @Test func asyncOperation() async throws {
91
+ let result = try await service.fetch()
92
+ #expect(result.isValid)
93
+ }
94
+ ```
95
+
96
+ ## Arrange-Act-Assert Pattern
97
+
98
+ Structure every test with clear phases:
99
+
100
+ ```swift
101
+ @Test func calculateTotal() {
102
+ // Given
103
+ let cart = ShoppingCart()
104
+ cart.add(Item(price: 10))
105
+ cart.add(Item(price: 20))
106
+
107
+ // When
108
+ let total = cart.calculateTotal()
109
+
110
+ // Then
111
+ #expect(total == 30)
112
+ }
113
+ ```
114
+
115
+ ## Assertions
116
+
117
+ ### #expect - Soft Assertion
118
+
119
+ Continues test execution after failure:
120
+
121
+ ```swift
122
+ @Test func multipleExpectations() {
123
+ let user = User(name: "Alice", age: 30)
124
+ #expect(user.name == "Alice") // If fails, test continues
125
+ #expect(user.age == 30) // This still runs
126
+ }
127
+ ```
128
+
129
+ ### #require - Hard Assertion
130
+
131
+ Stops test execution on failure:
132
+
133
+ ```swift
134
+ @Test func requireExample() throws {
135
+ let user = try #require(fetchUser()) // Stops if nil
136
+ #expect(user.name == "Alice")
137
+ }
138
+ ```
139
+
140
+ ### Error Testing
141
+
142
+ ```swift
143
+ @Test func throwsError() {
144
+ #expect(throws: ValidationError.self) {
145
+ try validate(invalidInput)
146
+ }
147
+ }
148
+
149
+ @Test func throwsSpecificError() {
150
+ #expect(throws: ValidationError.emptyField) {
151
+ try validate("")
152
+ }
153
+ }
154
+ ```
155
+
156
+ ## F.I.R.S.T. Principles
157
+
158
+ | Principle | Description | Application |
159
+ |-----------|-------------|-------------|
160
+ | **Fast** | Tests execute in milliseconds | Mock expensive operations |
161
+ | **Isolated** | Tests don't depend on each other | Fresh instance per test |
162
+ | **Repeatable** | Same result every time | Mock dates, network, external deps |
163
+ | **Self-Validating** | Auto-report pass/fail | Use `#expect`, never rely on `print()` |
164
+ | **Timely** | Write tests alongside code | Use parameterized tests for edge cases |
165
+
166
+ ## Test Double Quick Reference
167
+
168
+ Per [Martin Fowler's definition](https://martinfowler.com/articles/mocksArentStubs.html):
169
+
170
+ | Type | Purpose | Verification |
171
+ |------|---------|--------------|
172
+ | **Dummy** | Fill parameters, never used | N/A |
173
+ | **Fake** | Working implementation with shortcuts | State |
174
+ | **Stub** | Provides canned answers | State |
175
+ | **Spy** | Records calls for verification | State |
176
+ | **SpyingStub** | Stub + Spy combined (most common) | State |
177
+ | **Mock** | Pre-programmed expectations, self-verifies | Behavior |
178
+
179
+ **Important**: What Swift community calls "Mock" is usually a **SpyingStub**.
180
+
181
+ For detailed patterns, see `references/test-doubles.md`.
182
+
183
+ ## Test Double Placement
184
+
185
+ Place test doubles **close to the interface**, not in test targets:
186
+
187
+ ```swift
188
+ // In PersonalRecordsCore-Interface/Sources/...
189
+
190
+ public protocol PersonalRecordsRepositoryProtocol: Sendable {
191
+ func getAll() async throws -> [PersonalRecord]
192
+ func save(_ record: PersonalRecord) async throws
193
+ }
194
+
195
+ #if DEBUG
196
+ public final class PersonalRecordsRepositorySpyingStub: PersonalRecordsRepositoryProtocol {
197
+ // Spy: Captured calls
198
+ public private(set) var savedRecords: [PersonalRecord] = []
199
+
200
+ // Stub: Configurable responses
201
+ public var recordsToReturn: [PersonalRecord] = []
202
+ public var errorToThrow: Error?
203
+
204
+ public func getAll() async throws -> [PersonalRecord] {
205
+ if let error = errorToThrow { throw error }
206
+ return recordsToReturn
207
+ }
208
+
209
+ public func save(_ record: PersonalRecord) async throws {
210
+ if let error = errorToThrow { throw error }
211
+ savedRecords.append(record)
212
+ }
213
+ }
214
+ #endif
215
+ ```
216
+
217
+ ## Fixtures
218
+
219
+ Place fixtures **close to the model**:
220
+
221
+ ```swift
222
+ // In Sources/Models/PersonalRecord.swift
223
+
224
+ public struct PersonalRecord: Equatable, Sendable {
225
+ public let id: UUID
226
+ public let weight: Double
227
+ // ...
228
+ }
229
+
230
+ #if DEBUG
231
+ extension PersonalRecord {
232
+ public static func fixture(
233
+ id: UUID = UUID(),
234
+ weight: Double = 100.0
235
+ // ... defaults for all properties
236
+ ) -> PersonalRecord {
237
+ PersonalRecord(id: id, weight: weight)
238
+ }
239
+ }
240
+ #endif
241
+ ```
242
+
243
+ For detailed patterns, see `references/fixtures.md`.
244
+
245
+ ## Test Pyramid
246
+
247
+ ```
248
+ +-------------+
249
+ | UI Tests | 5% - End-to-end flows
250
+ | (E2E) |
251
+ +-------------+
252
+ | Integration | 15% - Module interactions
253
+ | Tests |
254
+ +-------------+
255
+ | Unit | 80% - Individual components
256
+ | Tests |
257
+ +-------------+
258
+ ```
259
+
260
+ ## Reference Files
261
+
262
+ Load these files as needed for specific topics:
263
+
264
+ - **`test-organization.md`** - Suites, tags, traits, parallel execution
265
+ - **`parameterized-tests.md`** - Testing multiple inputs efficiently
266
+ - **`async-testing.md`** - Async patterns, confirmation, timeouts, cancellation
267
+ - **`migration-xctest.md`** - Complete XCTest to Swift Testing migration guide
268
+ - **`test-doubles.md`** - Complete taxonomy with examples (Dummy, Fake, Stub, Spy, SpyingStub, Mock)
269
+ - **`fixtures.md`** - Fixture patterns, placement, and best practices
270
+ - **`integration-testing.md`** - Module interaction testing patterns
271
+ - **`snapshot-testing.md`** - UI regression testing with SnapshotTesting library
272
+ - **`dump-snapshot-testing.md`** - Text-based snapshot testing for data structures
273
+
274
+ ## Best Practices Summary
275
+
276
+ 1. **Use Swift Testing for new tests** - Modern syntax, better features
277
+ 2. **Follow Arrange-Act-Assert** - Clear test structure
278
+ 3. **Apply F.I.R.S.T. principles** - Fast, Isolated, Repeatable, Self-Validating, Timely
279
+ 4. **Place fixtures near models** - With `#if DEBUG` guards
280
+ 5. **Place test doubles near interfaces** - With `#if DEBUG` guards
281
+ 6. **Prefer state verification** - Simpler, less brittle than behavior verification
282
+ 7. **Use parameterized tests** - For testing multiple inputs efficiently
283
+ 8. **Follow test pyramid** - 80% unit, 15% integration, 5% UI
284
+
285
+ ## Verification Checklist (When You Write Tests)
286
+
287
+ - Tests follow Arrange-Act-Assert pattern
288
+ - Test names describe behavior, not implementation
289
+ - Fixtures use sensible defaults, not random values
290
+ - Test doubles are minimal (only stub what's needed)
291
+ - Async tests use proper patterns (async/await, confirmation)
292
+ - Tests are fast (mock expensive operations)
293
+ - Tests are isolated (no shared state)
294
+ - Tests are repeatable (no flaky date/time dependencies)
295
+
@@ -0,0 +1,245 @@
1
+ # Async Testing
2
+
3
+ Testing asynchronous code with Swift Testing.
4
+
5
+ ## Basic Async Tests
6
+
7
+ ```swift
8
+ @Test func asyncOperation() async {
9
+ let result = await service.fetch()
10
+ #expect(result.isValid)
11
+ }
12
+
13
+ @Test func asyncThrowingOperation() async throws {
14
+ let data = try await service.fetchData()
15
+ #expect(!data.isEmpty)
16
+ }
17
+ ```
18
+
19
+ ## Testing Async Sequences
20
+
21
+ ```swift
22
+ @Test func asyncSequence() async {
23
+ let sequence = Counter().values
24
+ var collected: [Int] = []
25
+
26
+ for await value in sequence.prefix(3) {
27
+ collected.append(value)
28
+ }
29
+
30
+ #expect(collected == [1, 2, 3])
31
+ }
32
+ ```
33
+
34
+ ## Confirmation (for callbacks/delegates)
35
+
36
+ Use `confirmation` when testing delegate patterns or callbacks:
37
+
38
+ ```swift
39
+ @Test func delegateCallback() async {
40
+ await confirmation { confirm in
41
+ let delegate = TestDelegate(onComplete: {
42
+ confirm()
43
+ })
44
+
45
+ service.delegate = delegate
46
+ service.performAction()
47
+ }
48
+ }
49
+ ```
50
+
51
+ ### Multiple Confirmations
52
+
53
+ ```swift
54
+ @Test func multipleCallbacks() async {
55
+ await confirmation(expectedCount: 3) { confirm in
56
+ let observer = Observer(onEvent: { _ in
57
+ confirm()
58
+ })
59
+
60
+ emitter.emit(.event1)
61
+ emitter.emit(.event2)
62
+ emitter.emit(.event3)
63
+ }
64
+ }
65
+ ```
66
+
67
+ ### Optional Confirmation
68
+
69
+ ```swift
70
+ @Test func optionalCallback() async {
71
+ await confirmation(expectedCount: 0...1) { confirm in
72
+ // May or may not be called
73
+ service.maybeNotify { confirm() }
74
+ }
75
+ }
76
+ ```
77
+
78
+ ## Testing Timeouts
79
+
80
+ ### Built-in Time Limit
81
+
82
+ ```swift
83
+ @Test(.timeLimit(.seconds(5)))
84
+ func mustCompleteQuickly() async {
85
+ await slowOperation()
86
+ }
87
+ ```
88
+
89
+ ### Custom Timeout
90
+
91
+ ```swift
92
+ @Test func withCustomTimeout() async throws {
93
+ try await withTimeout(seconds: 2) {
94
+ try await service.fetch()
95
+ }
96
+ }
97
+
98
+ func withTimeout<T>(
99
+ seconds: Double,
100
+ operation: @escaping () async throws -> T
101
+ ) async throws -> T {
102
+ try await withThrowingTaskGroup(of: T.self) { group in
103
+ group.addTask { try await operation() }
104
+ group.addTask {
105
+ try await Task.sleep(for: .seconds(seconds))
106
+ throw TimeoutError()
107
+ }
108
+ let result = try await group.next()!
109
+ group.cancelAll()
110
+ return result
111
+ }
112
+ }
113
+ ```
114
+
115
+ ## Testing Cancellation
116
+
117
+ ```swift
118
+ @Test func cancellation() async {
119
+ let task = Task {
120
+ try await longRunningOperation()
121
+ }
122
+
123
+ // Give it time to start
124
+ try? await Task.sleep(for: .milliseconds(100))
125
+
126
+ task.cancel()
127
+
128
+ do {
129
+ _ = try await task.value
130
+ Issue.record("Should have been cancelled")
131
+ } catch is CancellationError {
132
+ // Expected
133
+ }
134
+ }
135
+ ```
136
+
137
+ ## Testing Actors
138
+
139
+ ```swift
140
+ actor Counter {
141
+ var value = 0
142
+ func increment() { value += 1 }
143
+ }
144
+
145
+ @Test func actorState() async {
146
+ let counter = Counter()
147
+
148
+ await counter.increment()
149
+ await counter.increment()
150
+
151
+ let value = await counter.value
152
+ #expect(value == 2)
153
+ }
154
+ ```
155
+
156
+ ## Testing MainActor Code
157
+
158
+ ```swift
159
+ @MainActor
160
+ class ViewModel {
161
+ var items: [Item] = []
162
+ func load() async {
163
+ items = await fetchItems()
164
+ }
165
+ }
166
+
167
+ @Test @MainActor
168
+ func viewModelLoading() async {
169
+ let viewModel = ViewModel()
170
+ await viewModel.load()
171
+ #expect(!viewModel.items.isEmpty)
172
+ }
173
+ ```
174
+
175
+ ## Mocking Async Dependencies
176
+
177
+ ```swift
178
+ struct APIClient {
179
+ var fetch: @Sendable (URL) async throws -> Data
180
+ }
181
+
182
+ @Test func withMockedClient() async throws {
183
+ let mockData = "test".data(using: .utf8)!
184
+ let client = APIClient(
185
+ fetch: { _ in mockData }
186
+ )
187
+
188
+ let service = Service(client: client)
189
+ let result = try await service.getData()
190
+
191
+ #expect(result == mockData)
192
+ }
193
+ ```
194
+
195
+ ## Testing Debounced Operations
196
+
197
+ ```swift
198
+ @Test func debounce() async throws {
199
+ let debouncer = Debouncer(delay: .milliseconds(100))
200
+ var callCount = 0
201
+
202
+ // Rapid calls
203
+ for _ in 1...5 {
204
+ await debouncer.submit {
205
+ callCount += 1
206
+ }
207
+ }
208
+
209
+ // Wait for debounce
210
+ try await Task.sleep(for: .milliseconds(150))
211
+
212
+ #expect(callCount == 1) // Only last call executed
213
+ }
214
+ ```
215
+
216
+ ## Testing Retry Logic
217
+
218
+ ```swift
219
+ @Test func retryOnFailure() async throws {
220
+ var attempts = 0
221
+ let service = Service(
222
+ fetch: {
223
+ attempts += 1
224
+ if attempts < 3 {
225
+ throw NetworkError.timeout
226
+ }
227
+ return Data()
228
+ }
229
+ )
230
+
231
+ let result = try await service.fetchWithRetry(maxAttempts: 3)
232
+
233
+ #expect(attempts == 3)
234
+ #expect(result != nil)
235
+ }
236
+ ```
237
+
238
+ ## Best Practices
239
+
240
+ 1. **Use async/await directly**: No need for expectations/wait
241
+ 2. **Use confirmation for callbacks**: When testing delegate patterns
242
+ 3. **Set time limits**: Prevent hanging tests
243
+ 4. **Test cancellation**: Ensure proper cleanup
244
+ 5. **Mock async dependencies**: Use closures for testability
245
+ 6. **Run on correct actor**: Use @MainActor for UI tests