Vitest Mocking
Test Double Types
Type Purpose Changes Implementation?
Spy Record calls, verify interactions No (uses real code)
Mock Replace module entirely Yes
Stub Return canned responses Yes
Fake Working but simplified impl Yes
Decision Guide
Use spy when: Verifying a function was called correctly while keeping real behavior
const fetchSpy = vi.spyOn(globalThis, "fetch"); await doSomething(); expect(fetchSpy).toHaveBeenCalledWith("https://api.example.com");
Use mock when: Replacing external dependencies (network, database, third-party modules)
vi.mock("./api-client"); vi.mocked(apiClient.fetch).mockResolvedValue({ data: "test" });
Use stubGlobal when: Replacing browser/Node globals
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(mockResponse));
Essential Patterns
Mock Module (hoisted)
import { someFunc } from "./module"; vi.mock("./module"); // hoisted to top
it("test", () => { vi.mocked(someFunc).mockReturnValue("mocked"); });
Mock with Implementation
vi.mock("./module", () => ({ someFunc: vi.fn().mockReturnValue("mocked"), }));
Spy with Fake Return
const spy = vi.spyOn(obj, "method").mockReturnValue("fake");
Mock Global Fetch
globalThis.fetch = vi.fn().mockResolvedValue({ ok: true, json: async () => ({ data: "test" }), } as Response);
Fake Timers
beforeAll(() => vi.useFakeTimers()); afterAll(() => vi.useRealTimers());
it("test", async () => { // advance time await vi.advanceTimersByTimeAsync(5000); });
Clean Up
beforeEach(() => vi.clearAllMocks()); // reset call counts afterEach(() => vi.restoreAllMocks()); // restore originals
Reference
For complete examples including default imports, named imports, classes, partial mocks, snapshot testing, composables, and verification patterns, see references/cheat-sheet.md.