@@ -13,8 +13,12 @@ Use Vitest. Test files: `feature.ts` → `feature.test.ts`
1313These modules are mocked globally — do NOT re-mock them in test files unless you need to override behavior:
1414
1515- ` @sim/db ` → ` databaseMock `
16+ - ` @sim/db/schema ` → ` schemaMock `
1617- ` drizzle-orm ` → ` drizzleOrmMock `
1718- ` @sim/logger ` → ` loggerMock `
19+ - ` @/lib/auth ` → ` authMock `
20+ - ` @/lib/auth/hybrid ` → ` hybridAuthMock ` (with default session-delegating behavior)
21+ - ` @/lib/core/utils/request ` → ` requestUtilsMock `
1822- ` @/stores/console/store ` , ` @/stores/terminal ` , ` @/stores/execution/store `
1923- ` @/blocks/registry `
2024- ` @trigger.dev/sdk `
@@ -102,10 +106,6 @@ vi.mock('@/lib/workspaces/utils', () => ({
102106}))
103107```
104108
105- ### NEVER use ` mockAuth() ` , ` mockConsoleLogger() ` , or ` setupCommonApiMocks() ` from ` @sim/testing `
106-
107- These helpers internally use ` vi.doMock() ` which is slow. Use direct ` vi.hoisted() ` + ` vi.mock() ` instead.
108-
109109### Mock heavy transitive dependencies
110110
111111If a module under test imports ` @/blocks ` (200+ files), ` @/tools/registry ` , or other heavy modules, mock them:
@@ -135,83 +135,122 @@ await new Promise(r => setTimeout(r, 1))
135135vi .useFakeTimers ()
136136```
137137
138- ## Mock Pattern Reference
138+ ## Centralized Mocks (prefer over local declarations)
139+
140+ ` @sim/testing ` exports ready-to-use mock modules for common dependencies. Import and pass directly to ` vi.mock() ` — no ` vi.hoisted() ` boilerplate needed. Each paired ` *MockFns ` object exposes the underlying ` vi.fn() ` s for per-test overrides.
141+
142+ | Module mocked | Import | Factory form |
143+ | ---| ---| ---|
144+ | ` @/app/api/auth/oauth/utils ` | ` authOAuthUtilsMock ` , ` authOAuthUtilsMockFns ` | ` vi.mock('@/app/api/auth/oauth/utils', () => authOAuthUtilsMock) ` |
145+ | ` @/app/api/knowledge/utils ` | ` knowledgeApiUtilsMock ` , ` knowledgeApiUtilsMockFns ` | ` vi.mock('@/app/api/knowledge/utils', () => knowledgeApiUtilsMock) ` |
146+ | ` @/app/api/workflows/utils ` | ` workflowsApiUtilsMock ` , ` workflowsApiUtilsMockFns ` | ` vi.mock('@/app/api/workflows/utils', () => workflowsApiUtilsMock) ` |
147+ | ` @/lib/audit/log ` | ` auditMock ` , ` auditMockFns ` | ` vi.mock('@/lib/audit/log', () => auditMock) ` |
148+ | ` @/lib/auth ` | ` authMock ` , ` authMockFns ` | ` vi.mock('@/lib/auth', () => authMock) ` |
149+ | ` @/lib/auth/hybrid ` | ` hybridAuthMock ` , ` hybridAuthMockFns ` | ` vi.mock('@/lib/auth/hybrid', () => hybridAuthMock) ` |
150+ | ` @/lib/copilot/request/http ` | ` copilotHttpMock ` , ` copilotHttpMockFns ` | ` vi.mock('@/lib/copilot/request/http', () => copilotHttpMock) ` |
151+ | ` @/lib/core/config/env ` | ` envMock ` , ` createEnvMock(overrides) ` | ` vi.mock('@/lib/core/config/env', () => envMock) ` |
152+ | ` @/lib/core/config/feature-flags ` | ` featureFlagsMock ` | ` vi.mock('@/lib/core/config/feature-flags', () => featureFlagsMock) ` |
153+ | ` @/lib/core/config/redis ` | ` redisConfigMock ` , ` redisConfigMockFns ` | ` vi.mock('@/lib/core/config/redis', () => redisConfigMock) ` |
154+ | ` @/lib/core/security/encryption ` | ` encryptionMock ` , ` encryptionMockFns ` | ` vi.mock('@/lib/core/security/encryption', () => encryptionMock) ` |
155+ | ` @/lib/core/security/input-validation.server ` | ` inputValidationMock ` , ` inputValidationMockFns ` | ` vi.mock('@/lib/core/security/input-validation.server', () => inputValidationMock) ` |
156+ | ` @/lib/core/utils/request ` | ` requestUtilsMock ` , ` requestUtilsMockFns ` | ` vi.mock('@/lib/core/utils/request', () => requestUtilsMock) ` |
157+ | ` @/lib/core/utils/urls ` | ` urlsMock ` , ` urlsMockFns ` | ` vi.mock('@/lib/core/utils/urls', () => urlsMock) ` |
158+ | ` @/lib/execution/preprocessing ` | ` executionPreprocessingMock ` , ` executionPreprocessingMockFns ` | ` vi.mock('@/lib/execution/preprocessing', () => executionPreprocessingMock) ` |
159+ | ` @/lib/logs/execution/logging-session ` | ` loggingSessionMock ` , ` loggingSessionMockFns ` , ` LoggingSessionMock ` | ` vi.mock('@/lib/logs/execution/logging-session', () => loggingSessionMock) ` |
160+ | ` @/lib/workflows/orchestration ` | ` workflowsOrchestrationMock ` , ` workflowsOrchestrationMockFns ` | ` vi.mock('@/lib/workflows/orchestration', () => workflowsOrchestrationMock) ` |
161+ | ` @/lib/workflows/persistence/utils ` | ` workflowsPersistenceUtilsMock ` , ` workflowsPersistenceUtilsMockFns ` | ` vi.mock('@/lib/workflows/persistence/utils', () => workflowsPersistenceUtilsMock) ` |
162+ | ` @/lib/workflows/utils ` | ` workflowsUtilsMock ` , ` workflowsUtilsMockFns ` | ` vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) ` |
163+ | ` @/lib/workspaces/permissions/utils ` | ` permissionsMock ` , ` permissionsMockFns ` | ` vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) ` |
164+ | ` @sim/db/schema ` | ` schemaMock ` | ` vi.mock('@sim/db/schema', () => schemaMock) ` |
139165
140166### Auth mocking (API routes)
141167
142168``` typescript
143- const { mockGetSession } = vi .hoisted (() => ({
144- mockGetSession: vi .fn (),
145- }))
169+ import { authMock , authMockFns } from ' @sim/testing'
170+ import { beforeEach , describe , expect , it , vi } from ' vitest'
146171
147- vi .mock (' @/lib/auth' , () => ({
148- auth: { api: { getSession: vi .fn () } },
149- getSession: mockGetSession ,
150- }))
172+ vi .mock (' @/lib/auth' , () => authMock )
151173
152- // In tests:
153- mockGetSession .mockResolvedValue ({ user: { id: ' user-1' , email: ' test@example.com' } })
154- mockGetSession .mockResolvedValue (null ) // unauthenticated
174+ import { GET } from ' @/app/api/my-route/route'
175+
176+ beforeEach (() => {
177+ vi .clearAllMocks ()
178+ authMockFns .mockGetSession .mockResolvedValue ({ user: { id: ' user-1' } })
179+ })
155180```
156181
182+ Only define a local ` vi.mock('@/lib/auth', ...) ` if the module under test consumes exports outside the centralized shape (e.g., ` auth.api.verifyOneTimeToken ` , ` auth.api.resetPassword ` ).
183+
157184### Hybrid auth mocking
158185
159186``` typescript
160- const { mockCheckSessionOrInternalAuth } = vi .hoisted (() => ({
161- mockCheckSessionOrInternalAuth: vi .fn (),
162- }))
187+ import { hybridAuthMock , hybridAuthMockFns } from ' @sim/testing'
163188
164- vi .mock (' @/lib/auth/hybrid' , () => ({
165- checkSessionOrInternalAuth: mockCheckSessionOrInternalAuth ,
166- }))
189+ vi .mock (' @/lib/auth/hybrid' , () => hybridAuthMock )
167190
168191// In tests:
169- mockCheckSessionOrInternalAuth .mockResolvedValue ({
192+ hybridAuthMockFns . mockCheckSessionOrInternalAuth .mockResolvedValue ({
170193 success: true , userId: ' user-1' , authType: ' session' ,
171194})
172195```
173196
174197### Database chain mocking
175198
199+ Use the centralized ` dbChainMock ` + ` dbChainMockFns ` helpers — no ` vi.hoisted() ` or chain-wiring boilerplate needed.
200+
176201``` typescript
177- const { mockSelect, mockFrom, mockWhere } = vi .hoisted (() => ({
178- mockSelect: vi .fn (),
179- mockFrom: vi .fn (),
180- mockWhere: vi .fn (),
181- }))
202+ import { dbChainMock , dbChainMockFns , resetDbChainMock } from ' @sim/testing'
182203
183- vi .mock (' @sim/db' , () => ({
184- db: { select: mockSelect },
185- }))
204+ vi .mock (' @sim/db' , () => dbChainMock )
205+ // Spread for custom exports: vi.mock('@sim/db', () => ({ ...dbChainMock, myTable: {...} }))
186206
187207beforeEach (() => {
188- mockSelect .mockReturnValue ({ from: mockFrom })
189- mockFrom .mockReturnValue ({ where: mockWhere })
190- mockWhere .mockResolvedValue ([{ id: ' 1' , name: ' test' }])
208+ vi .clearAllMocks ()
209+ resetDbChainMock () // only needed if tests use permanent (non-`Once`) overrides
210+ })
211+
212+ it (' reads a row' , async () => {
213+ dbChainMockFns .limit .mockResolvedValueOnce ([{ id: ' 1' , name: ' test' }])
214+ // exercise code that hits db.select().from().where().limit()
215+ expect (dbChainMockFns .where ).toHaveBeenCalled ()
191216})
192217```
193218
219+ ** Default chains supported:**
220+ - ` select()/selectDistinct()/selectDistinctOn() → from() → where()/innerJoin()/leftJoin() → where() → limit()/orderBy()/returning()/groupBy() `
221+ - ` insert() → values() → returning()/onConflictDoUpdate()/onConflictDoNothing() `
222+ - ` update() → set() → where() → limit()/orderBy()/returning() `
223+ - ` delete() → where() → limit()/orderBy()/returning() `
224+ - ` db.execute() ` resolves ` [] `
225+ - ` db.transaction(cb) ` calls cb with ` dbChainMock.db `
226+
227+ All terminals default to ` Promise.resolve([]) ` . Override per-test with ` dbChainMockFns.<terminal>.mockResolvedValueOnce(...) ` .
228+
229+ Use ` resetDbChainMock() ` in ` beforeEach ` only when tests replace wiring with ` .mockReturnValue ` / ` .mockResolvedValue ` (permanent). Tests using only ` ...Once ` variants don't need it.
230+
194231## @sim/testing Package
195232
196233Always prefer over local test data.
197234
198235| Category | Utilities |
199236| ----------| -----------|
200- | ** Mocks** | ` loggerMock ` , ` databaseMock ` , ` drizzleOrmMock ` , ` setupGlobalFetchMock() ` |
237+ | ** Module mocks** | See "Centralized Mocks" table above |
238+ | ** Logger helpers** | ` loggerMock ` , ` createMockLogger() ` , ` getLoggerCalls() ` , ` clearLoggerMocks() ` |
239+ | ** Database helpers** | ` databaseMock ` , ` drizzleOrmMock ` , ` createMockDb() ` , ` createMockSql() ` , ` createMockSqlOperators() ` |
240+ | ** Fetch helpers** | ` setupGlobalFetchMock() ` , ` createMockFetch() ` , ` createMockResponse() ` , ` mockFetchError() ` |
201241| ** Factories** | ` createSession() ` , ` createWorkflowRecord() ` , ` createBlock() ` , ` createExecutionContext() ` |
202242| ** Builders** | ` WorkflowBuilder ` , ` ExecutionContextBuilder ` |
203243| ** Assertions** | ` expectWorkflowAccessGranted() ` , ` expectBlockExecuted() ` |
204- | ** Requests** | ` createMockRequest() ` , ` createEnvMock ()` |
244+ | ** Requests** | ` createMockRequest() ` , ` createMockFormDataRequest ()` |
205245
206246## Rules Summary
207247
2082481 . ` @vitest-environment node ` unless DOM is required
209- 2 . ` vi.hoisted() ` + ` vi.mock() ` + static imports — never ` vi.resetModules () ` + ` vi.doMock () ` + dynamic imports
210- 3 . ` vi.mock() ` calls before importing mocked modules
211- 4 . ` @sim/testing ` utilities over local mocks
249+ 2 . Prefer centralized mocks from ` @sim/testing ` (see table above) over local ` vi.hoisted () ` + ` vi.mock () ` boilerplate
250+ 3 . ` vi.hoisted() ` + ` vi. mock()` + static imports — never ` vi.resetModules() ` + ` vi.doMock() ` + dynamic imports
251+ 4 . ` vi.mock() ` calls before importing mocked modules
2122525 . ` beforeEach(() => vi.clearAllMocks()) ` to reset state — no redundant ` afterEach `
2132536 . No ` vi.importActual() ` — mock everything explicitly
214- 7 . No ` mockAuth() ` , ` mockConsoleLogger() ` , ` setupCommonApiMocks() ` — use direct mocks
215- 8 . Mock heavy deps (` @/blocks ` , ` @/tools/registry ` , ` @/triggers ` ) in tests that don't need them
216- 9 . Use absolute imports in test files
217- 10 . Avoid real timers — use 1ms delays or ` vi.useFakeTimers() `
254+ 7 . Mock heavy deps (` @/blocks ` , ` @/tools/registry ` , ` @/triggers ` ) in tests that don't need them
255+ 8 . Use absolute imports in test files
256+ 9 . Avoid real timers — use 1ms delays or ` vi.useFakeTimers() `
0 commit comments