Files
claudekit/.claude/skills/testing/vitest/references/mock-patterns.md
T

6.4 KiB

Vitest Mock Patterns

Catalog of mocking patterns for common testing scenarios.

1. Module Mock (Full)

Replace an entire module with mock implementations.

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

// Mock the entire module BEFORE importing code that uses it.
vi.mock("@/services/payment", () => ({
  chargeCard: vi.fn().mockResolvedValue({
    transactionId: "txn-123",
    status: "succeeded",
  }),
  refundCharge: vi.fn().mockResolvedValue({
    refundId: "ref-456",
    status: "refunded",
  }),
}));

import { chargeCard } from "@/services/payment";
import { checkout } from "@/services/checkout";

describe("checkout", () => {
  it("should charge the card and return success", async () => {
    const result = await checkout({ amount: 42, cardToken: "tok_test" });

    expect(chargeCard).toHaveBeenCalledWith({
      amount: 42,
      token: "tok_test",
    });
    expect(result.status).toBe("succeeded");
  });
});

2. Partial Module Mock

Mock only specific exports; keep the rest real.

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

vi.mock("@/utils/config", async (importOriginal) => {
  const actual = await importOriginal<typeof import("@/utils/config")>();
  return {
    ...actual,
    // Override only this one export
    getFeatureFlag: vi.fn().mockReturnValue(true),
  };
});

import { getFeatureFlag, parseConfig } from "@/utils/config";

describe("with feature flag enabled", () => {
  it("should use the new algorithm", () => {
    // getFeatureFlag is mocked, parseConfig is real
    expect(getFeatureFlag("new-algo")).toBe(true);
  });
});

3. Manual Mock Reset / Per-Test Overrides

import { describe, it, expect, vi, beforeEach } from "vitest";
import { fetchUser } from "@/api/users";

vi.mock("@/api/users");

// Type the mock for autocomplete
const mockFetchUser = vi.mocked(fetchUser);

beforeEach(() => {
  vi.resetAllMocks(); // Clear call history AND implementations
});

describe("user profile", () => {
  it("shows user data on success", async () => {
    mockFetchUser.mockResolvedValueOnce({ id: "1", name: "Alice" });
    // ...test
  });

  it("shows error on failure", async () => {
    mockFetchUser.mockRejectedValueOnce(new Error("Network error"));
    // ...test
  });
});

4. API Mock with MSW (Mock Service Worker)

Best for integration tests that should exercise real fetch/axios code.

// test/mocks/handlers.ts
import { http, HttpResponse } from "msw";

export const handlers = [
  http.get("/api/users/:id", ({ params }) => {
    return HttpResponse.json({ id: params.id, name: "Alice", email: "alice@example.com" });
  }),
  http.post("/api/users", async ({ request }) => {
    const body = await request.json();
    return HttpResponse.json({ id: "new-1", ...body }, { status: 201 });
  }),
];
// test/mocks/server.ts
import { setupServer } from "msw/node";
import { handlers } from "./handlers";

export const server = setupServer(...handlers);
// test/setup.ts (referenced in vitest.config.ts setupFiles)
import { afterAll, afterEach, beforeAll } from "vitest";
import { server } from "./mocks/server";

beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// Usage in tests -- override handlers per test
import { http, HttpResponse } from "msw";
import { server } from "../mocks/server";

it("handles server error", async () => {
  server.use(
    http.get("/api/users/:id", () => {
      return HttpResponse.json({ error: "Not found" }, { status: 404 });
    }),
  );
  // ...test error handling
});

5. Timer Mocks

Control setTimeout, setInterval, Date.now.

beforeEach(() => vi.useFakeTimers());
afterEach(() => vi.useRealTimers());

it("should call the function after the delay", () => {
  const fn = vi.fn();
  const debounced = debounce(fn, 300);

  debounced();
  expect(fn).not.toHaveBeenCalled();

  vi.advanceTimersByTime(300);
  expect(fn).toHaveBeenCalledOnce();
});

// Fake date: vi.setSystemTime(new Date("2025-01-15T12:00:00Z"))

6. Spy Patterns

Observe calls without replacing implementation.

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

describe("logging", () => {
  it("should log errors to console", () => {
    const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});

    logError("something went wrong");

    expect(consoleSpy).toHaveBeenCalledWith(
      expect.stringContaining("something went wrong"),
    );
    consoleSpy.mockRestore();
  });
});

// Spy on object method without changing behavior
it("should call save", () => {
  const repo = new UserRepository();
  const saveSpy = vi.spyOn(repo, "save");

  repo.createUser({ name: "Alice" });

  expect(saveSpy).toHaveBeenCalledOnce();
  expect(saveSpy).toHaveBeenCalledWith(expect.objectContaining({ name: "Alice" }));
});

7. Global / Window Mocks

// Mock window.location
vi.spyOn(window, "location", "get").mockReturnValue({ ...window.location, pathname: "/dashboard" });

// Mock localStorage
const storage: Record<string, string> = {};
vi.spyOn(Storage.prototype, "getItem").mockImplementation((key) => storage[key] ?? null);
vi.spyOn(Storage.prototype, "setItem").mockImplementation((key, val) => { storage[key] = val; });

// Mock fetch (when not using MSW)
global.fetch = vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ data: "test" }) });

8. Class Mock

vi.mock("@/services/analytics", () => ({
  AnalyticsClient: vi.fn().mockImplementation(() => ({
    track: vi.fn(),
    identify: vi.fn(),
    flush: vi.fn().mockResolvedValue(undefined),
  })),
}));

Quick Reference: Mock Functions

Method Purpose
vi.fn() Create a standalone mock function
vi.fn().mockReturnValue(x) Always return x
vi.fn().mockReturnValueOnce(x) Return x once, then default
vi.fn().mockResolvedValue(x) Return Promise.resolve(x)
vi.fn().mockRejectedValue(e) Return Promise.reject(e)
vi.fn().mockImplementation(fn) Use custom implementation
vi.spyOn(obj, "method") Spy on existing method
vi.mocked(fn) Type helper for mocked function
vi.mock("module") Auto-mock all exports
vi.resetAllMocks() Reset history and implementations
vi.restoreAllMocks() Restore original implementations
vi.clearAllMocks() Clear call history only