|
1 | 1 | --- |
2 | | -description: Testing patterns with Vitest |
| 2 | +description: Testing patterns with Vitest and @sim/testing |
3 | 3 | globs: ["apps/sim/**/*.test.ts", "apps/sim/**/*.test.tsx"] |
4 | 4 | --- |
5 | 5 |
|
6 | 6 | # Testing Patterns |
7 | 7 |
|
8 | | -Use Vitest. Test files live next to source: `feature.ts` → `feature.test.ts` |
| 8 | +Use Vitest. Test files: `feature.ts` → `feature.test.ts` |
9 | 9 |
|
10 | 10 | ## Structure |
11 | 11 |
|
12 | 12 | ```typescript |
13 | 13 | /** |
14 | | - * Tests for [feature name] |
15 | | - * |
16 | 14 | * @vitest-environment node |
17 | 15 | */ |
| 16 | +import { databaseMock, loggerMock } from '@sim/testing' |
| 17 | +import { describe, expect, it, vi } from 'vitest' |
18 | 18 |
|
19 | | -// 1. Mocks BEFORE imports |
20 | | -vi.mock('@sim/db', () => ({ db: { select: vi.fn() } })) |
| 19 | +vi.mock('@sim/db', () => databaseMock) |
21 | 20 | vi.mock('@sim/logger', () => loggerMock) |
22 | 21 |
|
23 | | -// 2. Imports AFTER mocks |
24 | | -import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest' |
25 | | -import { createSession, loggerMock } from '@sim/testing' |
26 | 22 | import { myFunction } from '@/lib/feature' |
27 | 23 |
|
28 | 24 | describe('myFunction', () => { |
29 | 25 | beforeEach(() => vi.clearAllMocks()) |
30 | | - |
31 | | - it('should do something', () => { |
32 | | - expect(myFunction()).toBe(expected) |
33 | | - }) |
34 | | - |
35 | | - it.concurrent('runs in parallel', () => { ... }) |
| 26 | + it.concurrent('isolated tests run in parallel', () => { ... }) |
36 | 27 | }) |
37 | 28 | ``` |
38 | 29 |
|
39 | 30 | ## @sim/testing Package |
40 | 31 |
|
41 | | -```typescript |
42 | | -// Factories - create test data |
43 | | -import { createBlock, createWorkflow, createSession } from '@sim/testing' |
| 32 | +Always prefer over local mocks. |
44 | 33 |
|
45 | | -// Mocks - pre-configured mocks |
46 | | -import { loggerMock, databaseMock, fetchMock } from '@sim/testing' |
47 | | - |
48 | | -// Builders - fluent API for complex objects |
49 | | -import { ExecutionBuilder, WorkflowBuilder } from '@sim/testing' |
50 | | -``` |
| 34 | +| Category | Utilities | |
| 35 | +|----------|-----------| |
| 36 | +| **Mocks** | `loggerMock`, `databaseMock`, `setupGlobalFetchMock()` | |
| 37 | +| **Factories** | `createSession()`, `createWorkflowRecord()`, `createBlock()`, `createExecutorContext()` | |
| 38 | +| **Builders** | `WorkflowBuilder`, `ExecutionContextBuilder` | |
| 39 | +| **Assertions** | `expectWorkflowAccessGranted()`, `expectBlockExecuted()` | |
51 | 40 |
|
52 | 41 | ## Rules |
53 | 42 |
|
54 | 43 | 1. `@vitest-environment node` directive at file top |
55 | | -2. **Mocks before imports** - `vi.mock()` calls must come first |
56 | | -3. Use `@sim/testing` factories over manual test data |
57 | | -4. `it.concurrent` for independent tests (faster) |
| 44 | +2. `vi.mock()` calls before importing mocked modules |
| 45 | +3. `@sim/testing` utilities over local mocks |
| 46 | +4. `it.concurrent` for isolated tests (no shared mutable state) |
58 | 47 | 5. `beforeEach(() => vi.clearAllMocks())` to reset state |
59 | | -6. Group related tests with nested `describe` blocks |
60 | | -7. Test file naming: `*.test.ts` (not `*.spec.ts`) |
| 48 | + |
| 49 | +## Hoisted Mocks |
| 50 | + |
| 51 | +For mutable mock references: |
| 52 | + |
| 53 | +```typescript |
| 54 | +const mockFn = vi.hoisted(() => vi.fn()) |
| 55 | +vi.mock('@/lib/module', () => ({ myFunction: mockFn })) |
| 56 | +mockFn.mockResolvedValue({ data: 'test' }) |
| 57 | +``` |
0 commit comments