@martinffx/atelier 1.4.0 → 3.0.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 (93) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +95 -220
  3. package/agents/architect.md +6 -6
  4. package/agents/oracle.md +1 -1
  5. package/agents/{recon.md → sentinel.md} +3 -3
  6. package/dist/atelier.js +167 -26
  7. package/package.json +2 -1
  8. package/skills/atelier-orchestrator/SKILL.md +180 -0
  9. package/skills/atelier-setup/SKILL.md +80 -0
  10. package/skills/code-review/SKILL.md +2 -2
  11. package/skills/code-review/references/reviewers.md +3 -3
  12. package/skills/code-review/references/rq.md +9 -9
  13. package/skills/code-subagents/SKILL.md +44 -75
  14. package/skills/code-subagents/references/batch-reviewer-prompt.md +44 -0
  15. package/skills/code-subagents/references/implementor-prompt.md +10 -11
  16. package/skills/spec-brainstorm/SKILL.md +21 -16
  17. package/skills/spec-finish/SKILL.md +14 -14
  18. package/skills/spec-implement/SKILL.md +42 -42
  19. package/skills/spec-plan/SKILL.md +72 -26
  20. package/skills/spec-plan/references/plan_template.md +77 -0
  21. package/skills/code-subagents/references/code-quality-reviewer-prompt.md +0 -48
  22. package/skills/code-subagents/references/spec-reviewer-prompt.md +0 -62
  23. package/skills/python-architecture/SKILL.md +0 -148
  24. package/skills/python-architecture/references/data-modeling.md +0 -558
  25. package/skills/python-architecture/references/ddd.md +0 -569
  26. package/skills/python-architecture/references/functional-core.md +0 -337
  27. package/skills/python-build-tools/SKILL.md +0 -153
  28. package/skills/python-build-tools/references/basedpyright.md +0 -321
  29. package/skills/python-build-tools/references/ruff.md +0 -225
  30. package/skills/python-build-tools/references/uv.md +0 -257
  31. package/skills/python-fastapi/SKILL.md +0 -184
  32. package/skills/python-fastapi/references/api-design.md +0 -563
  33. package/skills/python-fastapi/references/dependencies.md +0 -479
  34. package/skills/python-fastapi/references/middleware.md +0 -475
  35. package/skills/python-fastapi/references/validation.md +0 -497
  36. package/skills/python-modern-python/SKILL.md +0 -201
  37. package/skills/python-modern-python/references/async.md +0 -76
  38. package/skills/python-modern-python/references/pattern-matching.md +0 -89
  39. package/skills/python-modern-python/references/typing.md +0 -96
  40. package/skills/python-monorepo/SKILL.md +0 -146
  41. package/skills/python-monorepo/references/docker.md +0 -515
  42. package/skills/python-monorepo/references/namespace-packages.md +0 -426
  43. package/skills/python-monorepo/references/workspace-config.md +0 -341
  44. package/skills/python-sqlalchemy/SKILL.md +0 -119
  45. package/skills/python-sqlalchemy/references/async.md +0 -51
  46. package/skills/python-sqlalchemy/references/models.md +0 -67
  47. package/skills/python-sqlalchemy/references/queries.md +0 -57
  48. package/skills/python-temporal/SKILL.md +0 -128
  49. package/skills/python-temporal/references/patterns.md +0 -87
  50. package/skills/python-temporal/references/testing.md +0 -54
  51. package/skills/python-testing/SKILL.md +0 -161
  52. package/skills/python-testing/references/boundaries.md +0 -449
  53. package/skills/python-testing/references/mocking.md +0 -449
  54. package/skills/python-testing/references/pytest.md +0 -524
  55. package/skills/spec-orchestrator/SKILL.md +0 -145
  56. package/skills/typescript-api-design/SKILL.md +0 -396
  57. package/skills/typescript-api-design/references/error-responses.md +0 -469
  58. package/skills/typescript-build-tools/SKILL.md +0 -447
  59. package/skills/typescript-build-tools/references/biome.md +0 -436
  60. package/skills/typescript-build-tools/references/bun.md +0 -432
  61. package/skills/typescript-build-tools/references/turborepo.md +0 -426
  62. package/skills/typescript-build-tools/references/vitest.md +0 -518
  63. package/skills/typescript-drizzle-orm/SKILL.md +0 -398
  64. package/skills/typescript-drizzle-orm/references/cloudflare.md +0 -392
  65. package/skills/typescript-drizzle-orm/references/entity-pattern.md +0 -412
  66. package/skills/typescript-drizzle-orm/references/postgresql.md +0 -191
  67. package/skills/typescript-drizzle-orm/references/repository-pattern.md +0 -479
  68. package/skills/typescript-drizzle-orm/references/sqlite.md +0 -291
  69. package/skills/typescript-dynamodb-toolbox/SKILL.md +0 -370
  70. package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +0 -574
  71. package/skills/typescript-dynamodb-toolbox/references/error-handling.md +0 -495
  72. package/skills/typescript-dynamodb-toolbox/references/modeling.md +0 -332
  73. package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +0 -631
  74. package/skills/typescript-dynamodb-toolbox/references/testing.md +0 -718
  75. package/skills/typescript-dynamodb-toolbox/references/transactions.md +0 -423
  76. package/skills/typescript-effect-ts/SKILL.md +0 -341
  77. package/skills/typescript-effect-ts/references/core.md +0 -412
  78. package/skills/typescript-effect-ts/references/error-handling.md +0 -468
  79. package/skills/typescript-effect-ts/references/resources.md +0 -476
  80. package/skills/typescript-effect-ts/references/schema.md +0 -536
  81. package/skills/typescript-effect-ts/references/services.md +0 -461
  82. package/skills/typescript-fastify/SKILL.md +0 -405
  83. package/skills/typescript-fastify/references/plugins.md +0 -329
  84. package/skills/typescript-fastify/references/typeid.md +0 -315
  85. package/skills/typescript-functional-patterns/SKILL.md +0 -402
  86. package/skills/typescript-functional-patterns/references/adts.md +0 -497
  87. package/skills/typescript-functional-patterns/references/branded-types.md +0 -542
  88. package/skills/typescript-functional-patterns/references/migration-guide.md +0 -695
  89. package/skills/typescript-functional-patterns/references/option-result.md +0 -585
  90. package/skills/typescript-testing/SKILL.md +0 -353
  91. package/skills/typescript-testing/references/mocking.md +0 -675
  92. package/skills/typescript-testing/references/msw.md +0 -852
  93. package/skills/typescript-testing/references/snapshot-testing.md +0 -774
@@ -1,675 +0,0 @@
1
- # Mocking Patterns with Vitest
2
-
3
- Comprehensive guide to mocking modules, functions, classes, and dependencies in Vitest tests.
4
-
5
- ## Module Mocking
6
-
7
- ### Auto-mocking Entire Modules
8
-
9
- ```typescript
10
- import { vi } from 'vitest'
11
- import * as userRepo from './user-repository'
12
-
13
- // Auto-mock all exports
14
- vi.mock('./user-repository')
15
-
16
- // All functions are now mocks
17
- expect(vi.isMockFunction(userRepo.findById)).toBe(true)
18
- expect(vi.isMockFunction(userRepo.save)).toBe(true)
19
- ```
20
-
21
- ### Partial Module Mocking
22
-
23
- ```typescript
24
- vi.mock('./user-repository', () => ({
25
- // Keep real implementation
26
- ...vi.importActual('./user-repository'),
27
-
28
- // Mock specific functions
29
- findById: vi.fn(),
30
- save: vi.fn(),
31
- }))
32
-
33
- // Now some functions are real, some are mocked
34
- await userRepo.validateEmail('test@example.com') // real
35
- await userRepo.findById('123') // mocked
36
- ```
37
-
38
- ### Factory Functions
39
-
40
- ```typescript
41
- vi.mock('./database', () => {
42
- const mockDb = {
43
- connect: vi.fn().mockResolvedValue(undefined),
44
- disconnect: vi.fn().mockResolvedValue(undefined),
45
- query: vi.fn(),
46
- }
47
-
48
- return {
49
- createConnection: () => mockDb,
50
- Database: class MockDatabase {
51
- connect = mockDb.connect
52
- query = mockDb.query
53
- }
54
- }
55
- })
56
- ```
57
-
58
- ## Typed Mocks
59
-
60
- ### Creating Typed Mock Objects
61
-
62
- ```typescript
63
- import { vi } from 'vitest'
64
- import type { UserRepository } from './types'
65
-
66
- // Create fully typed mock
67
- const mockUserRepo = vi.mocked<UserRepository>({
68
- findById: vi.fn(),
69
- findByEmail: vi.fn(),
70
- save: vi.fn(),
71
- delete: vi.fn(),
72
- list: vi.fn(),
73
- })
74
-
75
- // Type-safe mock setup
76
- mockUserRepo.findById.mockResolvedValue({
77
- id: '123',
78
- name: 'Alice',
79
- email: 'alice@example.com',
80
- })
81
-
82
- // TypeScript catches type errors
83
- // @ts-expect-error - wrong return type
84
- mockUserRepo.findById.mockResolvedValue({ invalid: 'data' })
85
- ```
86
-
87
- ### Generic Typed Mocks
88
-
89
- ```typescript
90
- function createMockRepository<T>(): Repository<T> {
91
- return {
92
- findById: vi.fn(),
93
- save: vi.fn(),
94
- delete: vi.fn(),
95
- list: vi.fn(),
96
- }
97
- }
98
-
99
- // Use with specific types
100
- const userRepo = createMockRepository<User>()
101
- const orderRepo = createMockRepository<Order>()
102
-
103
- // Fully typed
104
- userRepo.findById.mockResolvedValue(user) // User type
105
- orderRepo.findById.mockResolvedValue(order) // Order type
106
- ```
107
-
108
- ## Mock Implementations
109
-
110
- ### Return Values
111
-
112
- ```typescript
113
- // Single return value
114
- mockFn.mockReturnValue(42)
115
- mockFn.mockResolvedValue(user) // async
116
-
117
- // One-time return values
118
- mockFn
119
- .mockReturnValueOnce(1)
120
- .mockReturnValueOnce(2)
121
- .mockReturnValue(3) // default for subsequent calls
122
-
123
- // Async one-time values
124
- mockFn
125
- .mockResolvedValueOnce(user1)
126
- .mockResolvedValueOnce(user2)
127
- ```
128
-
129
- ### Custom Implementations
130
-
131
- ```typescript
132
- // Simple logic
133
- mockFn.mockImplementation((x) => x * 2)
134
-
135
- // Async logic
136
- mockFn.mockImplementation(async (id) => {
137
- if (id === '123') return user
138
- throw new Error('Not found')
139
- })
140
-
141
- // Stateful logic
142
- let callCount = 0
143
- mockFn.mockImplementation(() => {
144
- callCount++
145
- if (callCount === 1) return 'first'
146
- if (callCount === 2) return 'second'
147
- return 'default'
148
- })
149
-
150
- // One-time implementation
151
- mockFn.mockImplementationOnce(() => 'once')
152
- ```
153
-
154
- ### Error Throwing
155
-
156
- ```typescript
157
- // Synchronous errors
158
- mockFn.mockImplementation(() => {
159
- throw new Error('Something went wrong')
160
- })
161
-
162
- // Async errors (rejected promises)
163
- mockFn.mockRejectedValue(new Error('Database error'))
164
-
165
- // One-time errors
166
- mockFn
167
- .mockRejectedValueOnce(new Error('First call fails'))
168
- .mockResolvedValue(user) // subsequent calls succeed
169
-
170
- // Conditional errors
171
- mockFn.mockImplementation(async (id) => {
172
- if (id === 'invalid') {
173
- throw new Error('Invalid ID')
174
- }
175
- return data
176
- })
177
- ```
178
-
179
- ## Spies
180
-
181
- ### Spying on Existing Functions
182
-
183
- ```typescript
184
- import { vi } from 'vitest'
185
- import { emailService } from './email-service'
186
-
187
- // Spy without replacing implementation
188
- const sendSpy = vi.spyOn(emailService, 'send')
189
-
190
- // Function still works normally
191
- await emailService.send({ to: 'alice@example.com', subject: 'Test' })
192
-
193
- // But you can assert on calls
194
- expect(sendSpy).toHaveBeenCalledOnce()
195
- expect(sendSpy).toHaveBeenCalledWith({
196
- to: 'alice@example.com',
197
- subject: 'Test',
198
- })
199
-
200
- // Temporarily override
201
- sendSpy.mockResolvedValueOnce({ messageId: 'mock-123' })
202
- await emailService.send({ to: 'bob@example.com', subject: 'Test' })
203
- ```
204
-
205
- ### Spying on Object Methods
206
-
207
- ```typescript
208
- const user = {
209
- name: 'Alice',
210
- greet() {
211
- return `Hello, ${this.name}`
212
- }
213
- }
214
-
215
- const greetSpy = vi.spyOn(user, 'greet')
216
-
217
- // Call real method
218
- expect(user.greet()).toBe('Hello, Alice')
219
-
220
- // Assert it was called
221
- expect(greetSpy).toHaveBeenCalled()
222
-
223
- // Override for specific test
224
- greetSpy.mockReturnValue('Mock greeting')
225
- expect(user.greet()).toBe('Mock greeting')
226
-
227
- // Restore original
228
- greetSpy.mockRestore()
229
- expect(user.greet()).toBe('Hello, Alice')
230
- ```
231
-
232
- ### Spying on Constructors
233
-
234
- ```typescript
235
- class Database {
236
- constructor(public url: string) {}
237
- async connect() { /* ... */ }
238
- }
239
-
240
- const DatabaseSpy = vi.spyOn(Database.prototype, 'connect')
241
-
242
- const db = new Database('postgres://localhost')
243
- await db.connect()
244
-
245
- expect(DatabaseSpy).toHaveBeenCalledOnce()
246
- ```
247
-
248
- ## Class Mocking
249
-
250
- ### Mocking Entire Classes
251
-
252
- ```typescript
253
- vi.mock('./database', () => {
254
- return {
255
- Database: vi.fn().mockImplementation(() => ({
256
- connect: vi.fn().mockResolvedValue(undefined),
257
- query: vi.fn().mockResolvedValue([]),
258
- disconnect: vi.fn().mockResolvedValue(undefined),
259
- }))
260
- }
261
- })
262
-
263
- import { Database } from './database'
264
-
265
- // Database is now a mock constructor
266
- const db = new Database('url')
267
- await db.connect()
268
-
269
- expect(Database).toHaveBeenCalledWith('url')
270
- expect(db.connect).toHaveBeenCalled()
271
- ```
272
-
273
- ### Mocking with Class Instances
274
-
275
- ```typescript
276
- class MockDatabase {
277
- connect = vi.fn().mockResolvedValue(undefined)
278
- query = vi.fn().mockResolvedValue([])
279
- disconnect = vi.fn().mockResolvedValue(undefined)
280
- }
281
-
282
- vi.mock('./database', () => ({
283
- Database: MockDatabase
284
- }))
285
-
286
- // Use in tests
287
- const db = new Database()
288
- await db.connect()
289
-
290
- expect(db.connect).toHaveBeenCalled()
291
- ```
292
-
293
- ## Dependency Injection Patterns
294
-
295
- ### Constructor Injection
296
-
297
- ```typescript
298
- class UserService {
299
- constructor(
300
- private repo: UserRepository,
301
- private email: EmailService,
302
- ) {}
303
-
304
- async createUser(data: CreateUserInput) {
305
- const user = await this.repo.save(data)
306
- await this.email.sendWelcome(user.email)
307
- return user
308
- }
309
- }
310
-
311
- // Test with mocked dependencies
312
- it('creates user and sends email', async () => {
313
- const mockRepo = vi.mocked<UserRepository>({
314
- save: vi.fn().mockResolvedValue(user)
315
- })
316
-
317
- const mockEmail = vi.mocked<EmailService>({
318
- sendWelcome: vi.fn().mockResolvedValue(undefined)
319
- })
320
-
321
- const service = new UserService(mockRepo, mockEmail)
322
- const result = await service.createUser(userData)
323
-
324
- expect(mockRepo.save).toHaveBeenCalledWith(userData)
325
- expect(mockEmail.sendWelcome).toHaveBeenCalledWith(user.email)
326
- expect(result).toEqual(user)
327
- })
328
- ```
329
-
330
- ### Factory Pattern
331
-
332
- ```typescript
333
- interface Services {
334
- userRepo: UserRepository
335
- emailService: EmailService
336
- }
337
-
338
- function createUserService(deps: Services) {
339
- return {
340
- async createUser(data: CreateUserInput) {
341
- const user = await deps.userRepo.save(data)
342
- await deps.emailService.sendWelcome(user.email)
343
- return user
344
- }
345
- }
346
- }
347
-
348
- // Test with mocked dependencies
349
- it('creates user with factory', async () => {
350
- const service = createUserService({
351
- userRepo: mockRepo,
352
- emailService: mockEmail,
353
- })
354
-
355
- await service.createUser(userData)
356
- expect(mockRepo.save).toHaveBeenCalled()
357
- })
358
- ```
359
-
360
- ## Mock Assertions
361
-
362
- ### Call Tracking
363
-
364
- ```typescript
365
- // Called at all
366
- expect(mockFn).toHaveBeenCalled()
367
- expect(mockFn).not.toHaveBeenCalled()
368
-
369
- // Call count
370
- expect(mockFn).toHaveBeenCalledTimes(3)
371
- expect(mockFn).toHaveBeenCalledOnce()
372
-
373
- // Call arguments
374
- expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2')
375
- expect(mockFn).toHaveBeenLastCalledWith('arg1', 'arg2')
376
- expect(mockFn).toHaveBeenNthCalledWith(2, 'arg1', 'arg2') // second call
377
-
378
- // Partial argument matching
379
- expect(mockFn).toHaveBeenCalledWith(
380
- expect.objectContaining({ id: '123' }),
381
- expect.any(Number)
382
- )
383
- ```
384
-
385
- ### Call Order
386
-
387
- ```typescript
388
- const mockA = vi.fn()
389
- const mockB = vi.fn()
390
-
391
- mockA('first')
392
- mockB('second')
393
- mockA('third')
394
-
395
- // Check call order
396
- expect(mockA.mock.calls[0][0]).toBe('first')
397
- expect(mockB.mock.calls[0][0]).toBe('second')
398
- expect(mockA.mock.calls[1][0]).toBe('third')
399
-
400
- // Or use invocationCallOrder
401
- expect(mockA.mock.invocationCallOrder[0]).toBeLessThan(
402
- mockB.mock.invocationCallOrder[0]
403
- )
404
- ```
405
-
406
- ### Return Values
407
-
408
- ```typescript
409
- mockFn.mockReturnValue('result')
410
- const result = mockFn()
411
-
412
- expect(mockFn).toHaveReturned()
413
- expect(mockFn).toHaveReturnedWith('result')
414
- expect(mockFn).toHaveLastReturnedWith('result')
415
- expect(mockFn).toHaveNthReturnedWith(1, 'result')
416
- ```
417
-
418
- ## Mock State Management
419
-
420
- ### Resetting Mocks
421
-
422
- ```typescript
423
- // Clear call history and results, keep implementation
424
- mockFn.mockClear()
425
-
426
- // Clear everything, reset to no implementation
427
- mockFn.mockReset()
428
-
429
- // Reset and restore to original implementation (for spies)
430
- spyFn.mockRestore()
431
-
432
- // In tests
433
- beforeEach(() => {
434
- vi.clearAllMocks() // Clear all mocks
435
- // or
436
- vi.resetAllMocks() // Reset all mocks
437
- })
438
-
439
- afterAll(() => {
440
- vi.restoreAllMocks() // Restore all spies
441
- })
442
- ```
443
-
444
- ### Mock State Inspection
445
-
446
- ```typescript
447
- mockFn('arg1', 'arg2')
448
- mockFn('arg3', 'arg4')
449
-
450
- // Access mock state
451
- console.log(mockFn.mock.calls) // [['arg1', 'arg2'], ['arg3', 'arg4']]
452
- console.log(mockFn.mock.results) // [{ type: 'return', value: ... }]
453
- console.log(mockFn.mock.lastCall) // ['arg3', 'arg4']
454
- console.log(mockFn.mock.calls.length) // 2
455
-
456
- // Check if it's a mock
457
- expect(vi.isMockFunction(mockFn)).toBe(true)
458
- ```
459
-
460
- ## Global Mocks
461
-
462
- ### Mocking Global Objects
463
-
464
- ```typescript
465
- // Mock global fetch
466
- global.fetch = vi.fn()
467
-
468
- // Type-safe global mock
469
- global.fetch = vi.fn<typeof fetch>()
470
-
471
- global.fetch.mockResolvedValue({
472
- ok: true,
473
- json: async () => ({ data: 'response' })
474
- } as Response)
475
-
476
- // In test
477
- const data = await fetch('/api/users')
478
- expect(global.fetch).toHaveBeenCalledWith('/api/users')
479
- ```
480
-
481
- ### Mocking Environment Variables
482
-
483
- ```typescript
484
- import { beforeEach, afterEach } from 'vitest'
485
-
486
- let originalEnv: NodeJS.ProcessEnv
487
-
488
- beforeEach(() => {
489
- originalEnv = process.env
490
- process.env = { ...originalEnv, NODE_ENV: 'test' }
491
- })
492
-
493
- afterEach(() => {
494
- process.env = originalEnv
495
- })
496
-
497
- it('uses test environment', () => {
498
- expect(process.env.NODE_ENV).toBe('test')
499
- })
500
- ```
501
-
502
- ### Mocking Timers
503
-
504
- ```typescript
505
- import { vi } from 'vitest'
506
-
507
- beforeEach(() => {
508
- vi.useFakeTimers()
509
- })
510
-
511
- afterEach(() => {
512
- vi.useRealTimers()
513
- })
514
-
515
- it('executes after delay', async () => {
516
- const callback = vi.fn()
517
-
518
- setTimeout(callback, 1000)
519
-
520
- // Time hasn't passed yet
521
- expect(callback).not.toHaveBeenCalled()
522
-
523
- // Advance time
524
- vi.advanceTimersByTime(1000)
525
-
526
- expect(callback).toHaveBeenCalled()
527
- })
528
-
529
- it('runs all timers', async () => {
530
- const callback = vi.fn()
531
-
532
- setTimeout(callback, 1000)
533
- setTimeout(callback, 2000)
534
-
535
- vi.runAllTimers()
536
-
537
- expect(callback).toHaveBeenCalledTimes(2)
538
- })
539
- ```
540
-
541
- ## Advanced Patterns
542
-
543
- ### Mocking Chains
544
-
545
- ```typescript
546
- const mockBuilder = {
547
- select: vi.fn().mockReturnThis(),
548
- where: vi.fn().mockReturnThis(),
549
- limit: vi.fn().mockReturnThis(),
550
- execute: vi.fn().mockResolvedValue([user]),
551
- }
552
-
553
- // Chainable API
554
- const result = await mockBuilder
555
- .select(['id', 'name'])
556
- .where({ active: true })
557
- .limit(10)
558
- .execute()
559
-
560
- expect(mockBuilder.select).toHaveBeenCalledWith(['id', 'name'])
561
- expect(mockBuilder.where).toHaveBeenCalledWith({ active: true })
562
- expect(mockBuilder.limit).toHaveBeenCalledWith(10)
563
- expect(result).toEqual([user])
564
- ```
565
-
566
- ### Mock Generators
567
-
568
- ```typescript
569
- function* numberGenerator() {
570
- yield 1
571
- yield 2
572
- yield 3
573
- }
574
-
575
- const mockGen = vi.fn(numberGenerator)
576
-
577
- const gen = mockGen()
578
- expect(gen.next().value).toBe(1)
579
- expect(gen.next().value).toBe(2)
580
-
581
- expect(mockGen).toHaveBeenCalledOnce()
582
- ```
583
-
584
- ### Async Iterators
585
-
586
- ```typescript
587
- async function* asyncGenerator() {
588
- yield 'a'
589
- yield 'b'
590
- }
591
-
592
- const mockAsyncGen = vi.fn(asyncGenerator)
593
-
594
- const gen = mockAsyncGen()
595
- expect((await gen.next()).value).toBe('a')
596
- expect((await gen.next()).value).toBe('b')
597
- ```
598
-
599
- ## Guidelines
600
-
601
- 1. **Use `vi.mocked<T>()` for type safety** - Ensures mocks match interface contracts
602
- 2. **Mock at module boundaries** - Mock external dependencies, not internal functions
603
- 3. **Prefer dependency injection** - Makes testing easier and code more flexible
604
- 4. **Clear mocks between tests** - Use `beforeEach(() => vi.clearAllMocks())`
605
- 5. **Use spies for real implementations** - When you need to verify calls but keep behavior
606
- 6. **One mock setup per test** - Avoid shared mock state across tests
607
- 7. **Explicit return values** - Set up mock returns explicitly, don't rely on defaults
608
- 8. **Test both success and failure** - Mock both resolved and rejected scenarios
609
- 9. **Verify call order when critical** - Use `mock.invocationCallOrder` for sequence testing
610
- 10. **Restore spies after tests** - Use `afterAll(() => vi.restoreAllMocks())`
611
- 11. **Use fake timers for time-based code** - More reliable than real delays
612
- 12. **Mock at the right level** - HTTP layer (MSW) vs module layer vs function layer
613
-
614
- ## Common Pitfalls
615
-
616
- ### Don't Mock What You Don't Own
617
-
618
- ```typescript
619
- // BAD - mocking third-party library internals
620
- vi.mock('express', () => ({
621
- Router: vi.fn() // fragile, breaks on updates
622
- }))
623
-
624
- // GOOD - create abstraction and mock that
625
- interface HttpRouter {
626
- get(path: string, handler: Function): void
627
- post(path: string, handler: Function): void
628
- }
629
-
630
- const mockRouter = vi.mocked<HttpRouter>({
631
- get: vi.fn(),
632
- post: vi.fn(),
633
- })
634
- ```
635
-
636
- ### Avoid Over-Mocking
637
-
638
- ```typescript
639
- // BAD - mocking everything
640
- const mockUserService = {
641
- create: vi.fn(),
642
- validate: vi.fn(),
643
- format: vi.fn(),
644
- serialize: vi.fn(),
645
- }
646
-
647
- // GOOD - mock external dependencies only
648
- const mockUserRepo = vi.mocked<UserRepository>({
649
- save: vi.fn()
650
- })
651
-
652
- const userService = new UserService(mockUserRepo) // test real logic
653
- ```
654
-
655
- ### Reset Mock State
656
-
657
- ```typescript
658
- // BAD - shared mock state
659
- const mockFn = vi.fn()
660
-
661
- it('test 1', () => {
662
- mockFn('arg1')
663
- expect(mockFn).toHaveBeenCalledOnce()
664
- })
665
-
666
- it('test 2', () => {
667
- // FAILS - mockFn still has call from test 1
668
- expect(mockFn).not.toHaveBeenCalled()
669
- })
670
-
671
- // GOOD - clear between tests
672
- beforeEach(() => {
673
- vi.clearAllMocks()
674
- })
675
- ```