This document covers every testing pattern and convention used in the Claude Code codebase. Tests use Vitest and follow specific patterns for resettability, determinism, and isolation.
- Vitest — Test runner (Jest-compatible API)
- Configuration in
vitest.config.* - Run with:
npm testornpx vitest
Since the codebase uses module-level state extensively, every stateful module provides a _reset*ForTesting() function:
/**
* Reset error log state for testing purposes only.
* @internal
*/
export function _resetErrorLogForTesting(): void {
errorLogSink = null
errorQueue.length = 0
inMemoryErrorLog = []
}- Prefix with
_(indicates internal/private) - Include
ForTestingsuffix - Tag with
@internalin JSDoc - Reset ALL module-level mutable state
import { _resetErrorLogForTesting } from '../src/utils/log.js'
beforeEach(() => {
_resetErrorLogForTesting()
})
afterEach(() => {
_resetErrorLogForTesting()
})Production code uses process.env.NODE_ENV === 'test' to alter behavior in tests:
function shouldLogDebugMessage(message: string): boolean {
// Don't write debug logs during tests (noisy, filesystem side effects)
if (process.env.NODE_ENV === 'test' && !isDebugToStdErr()) {
return false
}
// ... normal logic
}const sortedMatches = results
.map((_, i) => [_, stats[i]!] as const)
.sort((a, b) => {
if (process.env.NODE_ENV === 'test') {
// In tests, always sort by filename for deterministic results
return a[0].localeCompare(b[0])
}
// In production, sort by modification time
return b[1] - a[1]
})// Disable error reporting in tests
if (process.env.NODE_ENV === 'test') {
return
}Some modules export values exclusively for testing:
// src/tools/testing/
// Contains tool implementations only used in tests
export const TestingPermissionTool = buildTool({
name: 'TestingPermission',
// ... simplified for testing
})// Exported for testing purposes
export const getDebugFilter = memoize((): DebugFilter | null => { ... })Tests must produce deterministic results. Key patterns:
if (process.env.NODE_ENV === 'test') {
return a[0].localeCompare(b[0]) // Alphabetical, not by mtime
}Use fixed dates/timestamps in test fixtures instead of Date.now().
When randomness is needed, use deterministic seeds in tests.
Tests typically mirror the source structure:
src/
├── utils/
│ ├── errors.ts
│ └── __tests__/
│ └── errors.test.ts
├── tools/
│ ├── GrepTool/
│ │ ├── GrepTool.ts
│ │ └── __tests__/
│ │ └── GrepTool.test.ts
Or in a top-level test/ directory:
test/
├── utils/
│ └── errors.test.ts
├── tools/
│ └── GrepTool.test.ts
Use the _resetForTesting() helpers rather than mocking internals:
beforeEach(() => {
_resetErrorLogForTesting()
})beforeEach(() => {
process.env.NODE_ENV = 'test'
process.env.USER_TYPE = 'ant'
})
afterEach(() => {
delete process.env.USER_TYPE
})beforeEach(() => {
isDebugMode.cache.clear?.()
getDebugFilter.cache.clear?.()
})Follow descriptive test names that explain the behavior:
describe('toError', () => {
it('returns the same Error if given an Error', () => { ... })
it('wraps a string in a new Error', () => { ... })
it('converts undefined to Error("undefined")', () => { ... })
})
describe('isENOENT', () => {
it('returns true for ENOENT errors', () => { ... })
it('returns false for other errno codes', () => { ... })
it('returns false for non-Error values', () => { ... })
})it('handles abort signal', async () => {
const controller = new AbortController()
const promise = sleep(10000, controller.signal)
controller.abort()
await promise // Should resolve immediately
})it('rejects after timeout', async () => {
const slowPromise = new Promise(() => {}) // Never resolves
await expect(
withTimeout(slowPromise, 100, 'Timed out')
).rejects.toThrow('Timed out')
})it('searches for pattern in files', async () => {
const result = await GrepTool.call(
{ pattern: 'function', path: testDir },
mockContext,
)
expect(result.data.numFiles).toBeGreaterThan(0)
})it('compacts conversation', async () => {
const result = await call('', mockContext)
expect(result.type).toBe('compact')
})- Always reset module state in
beforeEach/afterEach - Use
NODE_ENV === 'test'guards for non-deterministic behavior - Sort deterministically in tests (by name, not by time)
- Don't test internal implementation — test public API behavior
- Mock at boundaries (filesystem, network) not at function level
- Clear memoize caches between tests to prevent state leakage
- Use descriptive test names that explain expected behavior
- Keep test files close to the code they test