Vitest Cheatsheet
Unit vs Integration Tests
Use this Vitest reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Definitions
| Unit Test | Integration Test | |
|---|---|---|
| Scope | Single function, class, or module | Multiple modules or layers working together |
| Dependencies | All external deps mocked | Real or real-ish deps (DB, HTTP, FS) |
| Speed | Very fast (ms) | Slower (hundreds of ms to seconds) |
| Isolation | Complete | Partial |
| Failure signal | Pinpoints exact function | Reveals wiring/contract bugs |
| Typical count | Many | Fewer |
Unit Test Example
// math.ts export function clamp(value: number, min: number, max: number): number { return Math.min(Math.max(value, min), max) }
// math.test.ts import { describe, it, expect } from 'vitest' import { clamp } from './math' describe('clamp', () => { it('returns value when in range', () => expect(clamp(5, 1, 10)).toBe(5)) it('clamps to min', () => expect(clamp(-5, 1, 10)).toBe(1)) it('clamps to max', () => expect(clamp(15, 1, 10)).toBe(10)) })
No mocks needed — pure function, no side effects.
Integration Test Example
// userService.ts — depends on DB and email service export async function registerUser(email: string, password: string) { const user = await db.users.create({ email, password: hash(password) }) await emailService.sendWelcome(user.email) return user }
// userService.integration.test.ts import { describe, it, expect, beforeAll, afterAll } from 'vitest' import { db } from './db' import { registerUser } from './userService' // Uses a real test DB, mocks only the email service vi.mock('./emailService', () => ({ sendWelcome: vi.fn() })) beforeAll(async () => { await db.migrate() }) afterAll(async () => { await db.destroy() }) describe('registerUser integration', () => { it('persists user to DB', async () => { const user = await registerUser('alice@test.com', 'pass') const found = await db.users.findByEmail('alice@test.com') expect(found).toMatchObject({ email: 'alice@test.com' }) }) })
Structuring Test Files
src/
services/
userService.ts
userService.unit.test.ts # mocks DB, tests logic
userService.integration.test.ts # real DB, tests wiring
utils/
format.ts
format.test.ts # pure — always unitOr use separate directories:
src/
tests/
unit/
userService.test.ts
integration/
userService.test.tsSeparating Test Runs in Config
// vitest.config.ts export default defineConfig({ test: { include: ['src/**/*.unit.test.ts'], }, }) // vitest.integration.config.ts export default defineConfig({ test: { include: ['src/**/*.integration.test.ts'], testTimeout: 30_000, globalSetup: './tests/globalSetup.ts', }, })
// package.json { "scripts": { "test:unit": "vitest run", "test:integration": "vitest run --config vitest.integration.config.ts" } }
Using Projects to Run Both
test.projects (the replacement for the removed vitest.workspace.ts):
// vitest.config.ts import { defineConfig } from 'vitest/config' export default defineConfig({ test: { projects: [ { test: { name: 'unit', include: ['**/*.unit.test.ts'], environment: 'node', }, }, { test: { name: 'integration', include: ['**/*.integration.test.ts'], testTimeout: 30_000, globalSetup: './tests/db-setup.ts', }, }, ], }, })
What to Mock in Each Test Type
Unit tests — mock everything external:
vi.mock('../db') vi.mock('../emailService') vi.mock('axios') it('calls db.create with hashed password', async () => { vi.mocked(db.users.create).mockResolvedValue({ id: '1', email: 'a@a.com' }) const user = await registerUser('a@a.com', 'plain') expect(db.users.create).toHaveBeenCalledWith( expect.objectContaining({ email: 'a@a.com' }) ) })
Integration tests — mock only infrastructure boundaries:
// Don't mock the DB — that's what we're testing // DO mock: external APIs, email, payment processors, file system vi.mock('../emailService') vi.mock('../stripe')
Test Pyramid Strategy
/\
/E2E\ — few, slow, expensive (Playwright/Cypress)
/------\
/Integr. \ — moderate, test wiring
/----------\
/ Unit \ — many, fast, test logic
/--------------\Aim for ~70% unit / ~20% integration / ~10% E2E as a starting point.
Environment Config per Test Type
// vitest.config.ts export default defineConfig({ test: { projects: [ { test: { name: 'unit-dom', environment: 'jsdom', include: ['**/*.unit.test.tsx'], }, }, { test: { name: 'integration-node', environment: 'node', include: ['**/*.integration.test.ts'], }, }, ], }, })
Integration Test Gotchas
- Database state leakage — always truncate or roll back between tests.
- Port conflicts — use dynamic ports or
pool: 'forks'to isolate. - Slow CI — run integration tests in a separate job, cache the DB image.
- Non-deterministic order — integration tests must be independent; never rely on insertion order.