Vitest cheatsheet.

Install

npm i -D vitest @vitest/coverage-v8

vitest.config.ts:

import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    globals: true,             // describe/test/expect global
    environment: "node",       // or "jsdom" / "happy-dom"
    coverage: {
      provider: "v8",
      reporter: ["text", "html"],
    },
    setupFiles: ["./test/setup.ts"],
  },
});

Basic test

import { describe, it, expect } from "vitest";

describe("add", () => {
  it("adds two numbers", () => {
    expect(add(1, 2)).toBe(3);
  });
  
  it.skip("skipped", () => {});
  it.only("focused", () => {});
  it.todo("pending");
});

Common matchers

expect(x).toBe(y);                       // ===
expect(x).toEqual(y);                    // deep equal
expect(x).toStrictEqual(y);              // also checks prototype
expect(x).toBeTruthy();
expect(x).toBeFalsy();
expect(x).toBeUndefined();
expect(x).toBeNull();
expect(x).toBeDefined();
expect(x).toBeNaN();

expect(arr).toContain(x);
expect(arr).toHaveLength(3);
expect(obj).toHaveProperty("a.b.c", 42);

expect(str).toMatch(/regex/);
expect(num).toBeGreaterThan(0);
expect(num).toBeCloseTo(0.3);

expect(fn).toThrow();
expect(fn).toThrow("message");
expect(fn).toThrow(SpecificError);

await expect(promise).resolves.toBe(42);
await expect(promise).rejects.toThrow();

Async

it("awaits", async () => {
  const x = await fetch("/x");
  expect(x.ok).toBe(true);
});

beforeEach / afterEach

beforeAll(() => {});
afterAll(() => {});
beforeEach(() => {});
afterEach(() => {});

Mocking

import { vi } from "vitest";

const fn = vi.fn();
fn(1); fn(2);
expect(fn).toHaveBeenCalled();
expect(fn).toHaveBeenCalledTimes(2);
expect(fn).toHaveBeenCalledWith(1);
expect(fn).toHaveBeenLastCalledWith(2);
fn.mock.calls;                   // [[1], [2]]
fn.mock.results;

mock implementation

const fn = vi.fn(() => 42);
const fn2 = vi.fn().mockReturnValue(42);
const fn3 = vi.fn().mockResolvedValue({ data: 1 });
const fn4 = vi.fn().mockRejectedValue(new Error("x"));

fn.mockImplementation((x: number) => x * 2);
fn.mockImplementationOnce(...);
fn.mockReturnValueOnce(...);

vi.mock (module)

vi.mock("./db", () => ({
  getUser: vi.fn().mockResolvedValue({ id: 1, name: "A" }),
}));

import { getUser } from "./db";

Partial mock (keep some real)

vi.mock("./mod", async (importOriginal) => {
  const actual = await importOriginal<typeof import("./mod")>();
  return { ...actual, expensiveOp: vi.fn() };
});

Spying

const spy = vi.spyOn(console, "log");
doStuff();
expect(spy).toHaveBeenCalledWith("hi");
spy.mockRestore();

Timers

vi.useFakeTimers();
vi.setSystemTime(new Date("2026-01-01"));

setTimeout(fn, 1000);
vi.advanceTimersByTime(1000);          // run timers
vi.runAllTimers();
vi.runOnlyPendingTimers();

vi.useRealTimers();

Snapshots

expect(obj).toMatchSnapshot();
expect(str).toMatchInlineSnapshot(`"hello"`);
vitest --update    # regenerate

test.each

it.each([
  [1, 2, 3],
  [4, 5, 9],
  [10, 20, 30],
])("add(%i, %i) = %i", (a, b, expected) => {
  expect(add(a, b)).toBe(expected);
});

Custom matchers

expect.extend({
  toBeWithinRange(actual, min, max) {
    const pass = actual >= min && actual <= max;
    return {
      pass,
      message: () => `expected ${actual} in [${min}, ${max}]`,
    };
  },
});

expect(5).toBeWithinRange(1, 10);

Module reset

beforeEach(() => {
  vi.resetModules();           // clears module cache
  vi.resetAllMocks();          // resets all mocks
  vi.clearAllMocks();          // clears call history only
});

Coverage

vitest --coverage

vitest.config.ts:

test: {
  coverage: {
    provider: "v8",
    reporter: ["text", "html", "json"],
    thresholds: {
      lines: 80,
      branches: 80,
      functions: 80,
      statements: 80,
    },
    include: ["src/**"],
    exclude: ["src/**/*.test.ts"],
  },
}

React component testing

npm i -D @testing-library/react @testing-library/jest-dom jsdom
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";

it("renders", () => {
  render(<Button label="Go" />);
  expect(screen.getByRole("button")).toHaveTextContent("Go");
});

vitest.config.ts:

test: { environment: "jsdom" }

Watch mode

vitest                       # watch
vitest run                   # single run
vitest --ui                  # UI dashboard

CI

- run: npm ci
- run: npm run test -- --coverage --reporter=verbose

Common mistakes

  • expect(fn).toBe(other) — for functions, use toEqual / toStrictEqual.
  • Mocking after the import — vi.mock is hoisted; works at top level only.
  • Forgetting vi.resetAllMocks() — mocks leak between tests.
  • Using fake timers without useRealTimers() cleanup — affects later tests.

Read this next

If you want my Vitest harness (mocks + factories), it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .