| name | typescript-vitest |
| description | Use when writing tests, setting up test infrastructure, creating mocks, or configuring Vitest in TypeScript projects. Covers test organization, mocking patterns, assertions, and test utilities. |
Vitest Testing
Use Vitest as the test runner for all TypeScript projects.
Setup
Dependencies
npm install -D vitest @vitest/coverage-v8
Configuration (vitest.config.ts)
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
pool: 'forks',
poolOptions: {
forks: {
singleFork: true,
},
},
maxConcurrency: 2,
testTimeout: 20_000,
slowTestThreshold: 10_000,
bail: 1,
coverage: {
provider: 'v8',
},
expandSnapshotDiff: true,
},
})
With Path Aliases
If the project uses TypeScript path aliases (e.g., ~/), add resolve:
import { defineConfig } from 'vitest/config'
import { resolve } from 'path'
export default defineConfig({
resolve: {
alias: {
'~': resolve(__dirname, 'src'),
},
},
test: {
},
})
Test File Organization
Naming Convention
- Unit tests:
*.test.ts — colocated next to the source file
- Integration/E2E tests:
*.e2e.test.ts or placed in a tests/ directory
Directory Structure
src/
feature/
feature.ts
feature.test.ts # unit test next to source
tests/
mocks/ # shared mock utilities
mock-server.ts
mock-data.ts
helpers/ # test helpers
setup.ts
integration/ # integration tests
api.e2e.test.ts
Writing Tests
Basic Structure
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
describe('FeatureName', () => {
beforeEach(() => {
})
afterEach(() => {
})
it('should do expected behavior', () => {
const result = doSomething()
expect(result).toBe(expected)
})
it('should handle edge case', () => {
expect(() => doSomething(badInput)).toThrow('Expected error message')
})
})
Test Naming
Use descriptive names that explain the behavior, not the implementation:
it('should return empty array when no items match filter')
it('should throw when connection string is invalid')
it('should retry failed requests up to 3 times')
it('test filter')
it('error case')
it('works')
Assertions
Prefer Specific Matchers
expect(value).toBe(exact)
expect(obj).toEqual(expected)
expect(obj).toMatchObject(partial)
expect(value).toBeTruthy()
expect(value).toBeFalsy()
expect(value).toBeNull()
expect(value).toBeUndefined()
expect(value).toBeDefined()
expect(value).toBeGreaterThan(3)
expect(value).toBeCloseTo(0.3, 5)
expect(str).toContain('substring')
expect(str).toMatch(/regex/)
expect(arr).toHaveLength(3)
expect(arr).toContain(item)
expect(arr).toEqual(expect.arrayContaining([a, b]))
expect(() => ()).()
( ()).()
( ()).()
(())..(value)
(())..()
Inline Snapshots
Use inline snapshots for complex output verification:
expect(formatOutput(data)).toMatchInlineSnapshot(`
"expected output here"
`)
Mocking
Module Mocks
import { vi } from 'vitest'
vi.mock('./database', () => ({
query: vi.fn().mockResolvedValue([]),
connect: vi.fn(),
}))
vi.mock('./config')
Function Spies
const spy = vi.fn()
spy.mockReturnValue(42)
spy.mockResolvedValue(data)
spy.mockImplementation((x) => x * 2)
expect(spy).toHaveBeenCalled()
expect(spy).toHaveBeenCalledWith('arg1', 'arg2')
expect(spy).toHaveBeenCalledTimes(3)
Mock HTTP Server (Integration Tests)
For testing HTTP clients or APIs, create a mock server utility:
import { createServer, Server, IncomingMessage, ServerResponse } from 'http'
type MockResponse =
| { statusCode: 200; body: unknown }
| { statusCode: 204 }
| { statusCode: 400 | 404 | 500 | 503; body?: unknown }
export function createMockServer(responses: MockResponse[]) {
let callIndex = 0
const calls: { method: string; url: string; body: string }[] = []
const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
const body = await readBody(req)
calls.push({ method: req.method!, url: req.url!, body })
const mock = responses[callIndex++]
(!mock) {
res.()
res.()
}
res.(mock., { : })
( mock && mock. !== ) {
res.(.(mock.))
} {
res.()
}
})
{
: <>( {
server.(, {
addr = server.() { : }
()
})
}),
: <>( server.( ())),
calls,
}
}
Timer Mocks
beforeEach(() => {
vi.useFakeTimers()
})
afterEach(() => {
vi.useRealTimers()
})
it('should debounce calls', async () => {
trigger()
vi.advanceTimersByTime(500)
expect(handler).toHaveBeenCalledTimes(1)
})
Test Utilities
Shared Setup
Create reusable test fixtures:
export function createTestContext() {
const logger = pino({ level: 'silent' })
const db = createTestDatabase()
return {
logger,
db,
async cleanup() {
await db.close()
},
}
}
Database Test Patterns
describe('UserRepository', () => {
let ctx: TestContext
beforeEach(async () => {
ctx = await createTestContext()
})
afterEach(async () => {
await ctx.cleanup()
})
it('should create user', async () => {
const user = await ctx.db.users.create({ name: 'Test' })
expect(user.id).toBeDefined()
})
})
Environment Variables in Tests
import { vi } from 'vitest'
beforeEach(() => {
vi.stubEnv('DATABASE_URL', 'postgres://localhost:5432/test')
})
afterEach(() => {
vi.unstubAllEnvs()
})
Package.json Scripts
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
}
}