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, usetoEqual/toStrictEqual.- Mocking after the import —
vi.mockis 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 .