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

HookRuns
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:

  1. Outer beforeAll
  2. Outer beforeEach
  3. Inner beforeEach
  4. Test
  5. Inner afterEach
  6. Outer afterEach
  7. (after all tests) Inner afterAll
  8. (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()
}

globalSetup runs 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()
})