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 TestIntegration Test
ScopeSingle function, class, or moduleMultiple modules or layers working together
DependenciesAll external deps mockedReal or real-ish deps (DB, HTTP, FS)
SpeedVery fast (ms)Slower (hundreds of ms to seconds)
IsolationCompletePartial
Failure signalPinpoints exact functionReveals wiring/contract bugs
Typical countManyFewer

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 unit

Or use separate directories:

src/
tests/
  unit/
    userService.test.ts
  integration/
    userService.test.ts

Separating 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.