mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
* improvement(tests): speed up unit tests by eliminating vi.resetModules anti-pattern - convert 51 test files from vi.resetModules/vi.doMock/dynamic import to vi.hoisted/vi.mock/static import - add global @sim/db mock to vitest.setup.ts - switch 4 test files from jsdom to node environment - remove all vi.importActual calls that loaded heavy modules (200+ block files) - remove slow mockConsoleLogger/mockAuth/setupCommonApiMocks helpers - reduce real setTimeout delays in engine tests - mock heavy transitive deps in diff-engine test test execution time: 34s -> 9s (3.9x faster) environment time: 2.5s -> 0.6s (4x faster) * docs(testing): update testing best practices with performance rules - document vi.hoisted + vi.mock + static import as the standard pattern - explicitly ban vi.resetModules, vi.doMock, vi.importActual, mockAuth, setupCommonApiMocks - document global mocks from vitest.setup.ts - add mock pattern reference for auth, hybrid auth, and database chains - add performance rules section covering heavy deps, jsdom vs node, real timers * fix(tests): fix 4 failing test files with missing mocks - socket/middleware/permissions: add vi.mock for @/lib/auth to prevent transitive getBaseUrl() call - workflow-handler: add vi.mock for @/executor/utils/http matching executor mock pattern - evaluator-handler: add db.query.account mock structure before vi.spyOn - router-handler: same db.query.account fix as evaluator * fix(tests): replace banned Function type with explicit callback signature
217 lines
6.1 KiB
Plaintext
217 lines
6.1 KiB
Plaintext
---
|
|
description: Testing patterns with Vitest and @sim/testing
|
|
globs: ["apps/sim/**/*.test.ts", "apps/sim/**/*.test.tsx"]
|
|
---
|
|
|
|
# Testing Patterns
|
|
|
|
Use Vitest. Test files: `feature.ts` → `feature.test.ts`
|
|
|
|
## Global Mocks (vitest.setup.ts)
|
|
|
|
These modules are mocked globally — do NOT re-mock them in test files unless you need to override behavior:
|
|
|
|
- `@sim/db` → `databaseMock`
|
|
- `drizzle-orm` → `drizzleOrmMock`
|
|
- `@sim/logger` → `loggerMock`
|
|
- `@/stores/console/store`, `@/stores/terminal`, `@/stores/execution/store`
|
|
- `@/blocks/registry`
|
|
- `@trigger.dev/sdk`
|
|
|
|
## Structure
|
|
|
|
```typescript
|
|
/**
|
|
* @vitest-environment node
|
|
*/
|
|
import { createMockRequest } from '@sim/testing'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const { mockGetSession } = vi.hoisted(() => ({
|
|
mockGetSession: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@/lib/auth', () => ({
|
|
auth: { api: { getSession: vi.fn() } },
|
|
getSession: mockGetSession,
|
|
}))
|
|
|
|
import { GET, POST } from '@/app/api/my-route/route'
|
|
|
|
describe('my route', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
mockGetSession.mockResolvedValue({ user: { id: 'user-1' } })
|
|
})
|
|
|
|
it('returns data', async () => {
|
|
const req = createMockRequest('GET')
|
|
const res = await GET(req)
|
|
expect(res.status).toBe(200)
|
|
})
|
|
})
|
|
```
|
|
|
|
## Performance Rules (Critical)
|
|
|
|
### NEVER use `vi.resetModules()` + `vi.doMock()` + `await import()`
|
|
|
|
This is the #1 cause of slow tests. It forces complete module re-evaluation per test.
|
|
|
|
```typescript
|
|
// BAD — forces module re-evaluation every test (~50-100ms each)
|
|
beforeEach(() => {
|
|
vi.resetModules()
|
|
vi.doMock('@/lib/auth', () => ({ getSession: vi.fn() }))
|
|
})
|
|
it('test', async () => {
|
|
const { GET } = await import('./route') // slow dynamic import
|
|
})
|
|
|
|
// GOOD — module loaded once, mocks reconfigured per test (~1ms each)
|
|
const { mockGetSession } = vi.hoisted(() => ({
|
|
mockGetSession: vi.fn(),
|
|
}))
|
|
vi.mock('@/lib/auth', () => ({ getSession: mockGetSession }))
|
|
import { GET } from '@/app/api/my-route/route'
|
|
|
|
beforeEach(() => { vi.clearAllMocks() })
|
|
it('test', () => {
|
|
mockGetSession.mockResolvedValue({ user: { id: '1' } })
|
|
})
|
|
```
|
|
|
|
**Only exception:** Singleton modules that cache state at module scope (e.g., Redis clients, connection pools). These genuinely need `vi.resetModules()` + dynamic import to get a fresh instance per test.
|
|
|
|
### NEVER use `vi.importActual()`
|
|
|
|
This defeats the purpose of mocking by loading the real module and all its dependencies.
|
|
|
|
```typescript
|
|
// BAD — loads real module + all transitive deps
|
|
vi.mock('@/lib/workspaces/utils', async () => {
|
|
const actual = await vi.importActual('@/lib/workspaces/utils')
|
|
return { ...actual, myFn: vi.fn() }
|
|
})
|
|
|
|
// GOOD — mock everything, only implement what tests need
|
|
vi.mock('@/lib/workspaces/utils', () => ({
|
|
myFn: vi.fn(),
|
|
otherFn: vi.fn(),
|
|
}))
|
|
```
|
|
|
|
### NEVER use `mockAuth()`, `mockConsoleLogger()`, or `setupCommonApiMocks()` from `@sim/testing`
|
|
|
|
These helpers internally use `vi.doMock()` which is slow. Use direct `vi.hoisted()` + `vi.mock()` instead.
|
|
|
|
### Mock heavy transitive dependencies
|
|
|
|
If a module under test imports `@/blocks` (200+ files), `@/tools/registry`, or other heavy modules, mock them:
|
|
|
|
```typescript
|
|
vi.mock('@/blocks', () => ({
|
|
getBlock: () => null,
|
|
getAllBlocks: () => ({}),
|
|
getAllBlockTypes: () => [],
|
|
registry: {},
|
|
}))
|
|
```
|
|
|
|
### Use `@vitest-environment node` unless DOM is needed
|
|
|
|
Only use `@vitest-environment jsdom` if the test uses `window`, `document`, `FormData`, or other browser APIs. Node environment is significantly faster.
|
|
|
|
### Avoid real timers in tests
|
|
|
|
```typescript
|
|
// BAD
|
|
await new Promise(r => setTimeout(r, 500))
|
|
|
|
// GOOD — use minimal delays or fake timers
|
|
await new Promise(r => setTimeout(r, 1))
|
|
// or
|
|
vi.useFakeTimers()
|
|
```
|
|
|
|
## Mock Pattern Reference
|
|
|
|
### Auth mocking (API routes)
|
|
|
|
```typescript
|
|
const { mockGetSession } = vi.hoisted(() => ({
|
|
mockGetSession: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@/lib/auth', () => ({
|
|
auth: { api: { getSession: vi.fn() } },
|
|
getSession: mockGetSession,
|
|
}))
|
|
|
|
// In tests:
|
|
mockGetSession.mockResolvedValue({ user: { id: 'user-1', email: 'test@example.com' } })
|
|
mockGetSession.mockResolvedValue(null) // unauthenticated
|
|
```
|
|
|
|
### Hybrid auth mocking
|
|
|
|
```typescript
|
|
const { mockCheckSessionOrInternalAuth } = vi.hoisted(() => ({
|
|
mockCheckSessionOrInternalAuth: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@/lib/auth/hybrid', () => ({
|
|
checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth,
|
|
}))
|
|
|
|
// In tests:
|
|
mockCheckSessionOrInternalAuth.mockResolvedValue({
|
|
success: true, userId: 'user-1', authType: 'session',
|
|
})
|
|
```
|
|
|
|
### Database chain mocking
|
|
|
|
```typescript
|
|
const { mockSelect, mockFrom, mockWhere } = vi.hoisted(() => ({
|
|
mockSelect: vi.fn(),
|
|
mockFrom: vi.fn(),
|
|
mockWhere: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@sim/db', () => ({
|
|
db: { select: mockSelect },
|
|
}))
|
|
|
|
beforeEach(() => {
|
|
mockSelect.mockReturnValue({ from: mockFrom })
|
|
mockFrom.mockReturnValue({ where: mockWhere })
|
|
mockWhere.mockResolvedValue([{ id: '1', name: 'test' }])
|
|
})
|
|
```
|
|
|
|
## @sim/testing Package
|
|
|
|
Always prefer over local test data.
|
|
|
|
| Category | Utilities |
|
|
|----------|-----------|
|
|
| **Mocks** | `loggerMock`, `databaseMock`, `drizzleOrmMock`, `setupGlobalFetchMock()` |
|
|
| **Factories** | `createSession()`, `createWorkflowRecord()`, `createBlock()`, `createExecutionContext()` |
|
|
| **Builders** | `WorkflowBuilder`, `ExecutionContextBuilder` |
|
|
| **Assertions** | `expectWorkflowAccessGranted()`, `expectBlockExecuted()` |
|
|
| **Requests** | `createMockRequest()`, `createEnvMock()` |
|
|
|
|
## Rules Summary
|
|
|
|
1. `@vitest-environment node` unless DOM is required
|
|
2. `vi.hoisted()` + `vi.mock()` + static imports — never `vi.resetModules()` + `vi.doMock()` + dynamic imports
|
|
3. `vi.mock()` calls before importing mocked modules
|
|
4. `@sim/testing` utilities over local mocks
|
|
5. `beforeEach(() => vi.clearAllMocks())` to reset state — no redundant `afterEach`
|
|
6. No `vi.importActual()` — mock everything explicitly
|
|
7. No `mockAuth()`, `mockConsoleLogger()`, `setupCommonApiMocks()` — use direct mocks
|
|
8. Mock heavy deps (`@/blocks`, `@/tools/registry`, `@/triggers`) in tests that don't need them
|
|
9. Use absolute imports in test files
|
|
10. Avoid real timers — use 1ms delays or `vi.useFakeTimers()`
|