Vitest Cheatsheet
Setup and Teardown
Use this Vitest reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Lifecycle Hooks Overview
| Hook | Runs |
|---|---|
beforeAll(fn) | Once before all tests in the current suite |
afterAll(fn) | Once after all tests in the current suite |
beforeEach(fn) | Before every test in the current suite |
afterEach(fn) | After every test in the current suite |
import { beforeAll, afterAll, beforeEach, afterEach, describe, it, expect } from 'vitest' describe('Database tests', () => { let db: Database beforeAll(async () => { db = await Database.connect() }) afterAll(async () => { await db.close() }) beforeEach(async () => { await db.seed() }) afterEach(async () => { await db.truncate() }) it('finds a user', async () => { const user = await db.findUser(1) expect(user).toBeDefined() }) })
Scope: Global vs Suite-Level
Hooks defined outside any describe apply to all tests in the file. Hooks inside a describe apply only to that suite.
// File-level — runs for every test in this file beforeEach(() => { resetState() }) describe('Suite A', () => { beforeEach(() => { // runs AFTER the file-level beforeEach setupA() }) it('test 1', () => { /* ... */ }) }) describe('Suite B', () => { it('test 2', () => { /* ... */ }) // only file-level beforeEach runs })
Execution order for nested suites:
- Outer
beforeAll - Outer
beforeEach - Inner
beforeEach - Test
- Inner
afterEach - Outer
afterEach - (after all tests) Inner
afterAll - (after all tests) Outer
afterAll
Async Hooks
beforeAll(async () => { await startServer() }) afterAll(async () => { await stopServer() })
No done Callback
Vitest hooks (and tests) never accept Jest's done callback — beforeEach((done) => { ... }) receives context, not a completion function, and calling it throws. Return or await a Promise instead:
beforeEach(async () => { await new Promise<void>((resolve, reject) => { db.connect((err) => (err ? reject(err) : resolve())) }) })
Cleanup Inside Tests with onTestFinished
import { it, onTestFinished } from 'vitest' it('opens a file', () => { const handle = openFile('./data.txt') onTestFinished(() => handle.close()) // test logic... })
onTestFinished runs even if the test fails.
onTestFailed — Run Only on Failure
import { it, onTestFailed } from 'vitest' it('integration test', async () => { onTestFailed(async () => { await dumpLogs('./debug.log') }) // ... })
Resetting Mocks in Hooks
import { vi, afterEach, beforeEach } from 'vitest' beforeEach(() => { vi.useFakeTimers() }) afterEach(() => { vi.useRealTimers() vi.restoreAllMocks() })
Or declaratively in config (applies globally):
export default defineConfig({ test: { restoreMocks: true, // vi.restoreAllMocks() after each test clearMocks: true, // vi.clearAllMocks() after each test mockReset: false, // vi.resetAllMocks() after each test }, })
setupFiles — Global Setup Across All Tests
// vitest.config.ts export default defineConfig({ test: { setupFiles: ['./src/test/setup.ts'], }, })
// src/test/setup.ts import { beforeAll, afterAll, vi } from 'vitest' import { server } from './mocks/server' beforeAll(() => server.listen({ onUnhandledRequest: 'error' })) afterEach(() => server.resetHandlers()) afterAll(() => server.close())
globalSetup — Node-Level Setup (Runs Once Per Worker)
// vitest.config.ts export default defineConfig({ test: { globalSetup: './src/test/globalSetup.ts', }, })
// src/test/globalSetup.ts export async function setup() { // start a test database, docker container, etc. await startTestDb() } export async function teardown() { await stopTestDb() }
globalSetupruns in the main process (not in worker threads). Cannot use Vitest APIs (vi,expect) here.
Providing Data from globalSetup to Tests
// globalSetup.ts export async function setup() { const url = await startServer() process.env.TEST_SERVER_URL = url // pass via env }
// test file const baseUrl = process.env.TEST_SERVER_URL!
Fixture Pattern via test.extend
import { test } from 'vitest' type Fixtures = { user: { id: number; name: string } } const authTest = test.extend<Fixtures>({ user: async ({}, use) => { const user = await createTestUser() await use(user) await deleteTestUser(user.id) // teardown }, }) authTest('accesses user fixture', ({ user }) => { expect(user.name).toBeDefined() })