* 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
6.0 KiB
paths
| paths | ||
|---|---|---|
|
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→databaseMockdrizzle-orm→drizzleOrmMock@sim/logger→loggerMock@/stores/console/store,@/stores/terminal,@/stores/execution/store@/blocks/registry@trigger.dev/sdk
Structure
/**
* @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.
// 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.
// 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:
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
// 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)
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
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
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
@vitest-environment nodeunless DOM is requiredvi.hoisted()+vi.mock()+ static imports — nevervi.resetModules()+vi.doMock()+ dynamic importsvi.mock()calls before importing mocked modules@sim/testingutilities over local mocksbeforeEach(() => vi.clearAllMocks())to reset state — no redundantafterEach- No
vi.importActual()— mock everything explicitly - No
mockAuth(),mockConsoleLogger(),setupCommonApiMocks()— use direct mocks - Mock heavy deps (
@/blocks,@/tools/registry,@/triggers) in tests that don't need them - Use absolute imports in test files
- Avoid real timers — use 1ms delays or
vi.useFakeTimers()