Claude Code for Vitest Advanced: Mocking, Coverage, and Workspace Config — Claude Skills 360 Blog
Blog / Testing / Claude Code for Vitest Advanced: Mocking, Coverage, and Workspace Config
Testing

Claude Code for Vitest Advanced: Mocking, Coverage, and Workspace Config

Published: February 24, 2027
Read time: 8 min read
By: Claude Skills 360

Vitest is a Vite-native test framework — it reuses your vite.config.ts and runs tests with the same transform pipeline as your app. vi.mock("module") replaces a module entirely; vi.spyOn(obj, "method") intercepts a method call while keeping the original. vi.fn() creates a mock function with call tracking. vi.useFakeTimers() controls setTimeout, setInterval, Date, and performance.now for deterministic time tests. Snapshot testing with toMatchSnapshot() and toMatchInlineSnapshot() locks component output. Coverage with @vitest/coverage-v8 reports line, branch, and function coverage. Workspace config runs multiple test environments in one vitest invocation. Browser mode uses real Playwright or WebdriverIO for DOM tests without JSDOM limitations. test.each parameterizes test cases from arrays or tagged template literals. Claude Code generates Vitest configuration, mock patterns, coverage setups, and the workspace configurations for multi-package repositories.

CLAUDE.md for Vitest

## Vitest Stack
- Version: vitest >= 2.0, @vitest/coverage-v8 >= 2.0
- Mock: vi.mock("path", factory) — hoisted, replaces module in all tests
- Spy: vi.spyOn(object, "method").mockReturnValue(value) — intercept + restore
- Fn: vi.fn().mockImplementation(fn) — assertable, resettable mock
- Timers: vi.useFakeTimers() → vi.advanceTimersByTime(ms) → vi.useRealTimers()
- Workspace: vitest.workspace.ts — run unit + e2e + browser in different envs
- Coverage: @vitest/coverage-v8 — provider: "v8", thresholds in config
- Parameterize: test.each([[input, expected]]) or it.each`template`

Configuration

// vitest.config.ts — root configuration
import { defineConfig } from "vitest/config"
import react from "@vitejs/plugin-react"
import tsconfigPaths from "vite-tsconfig-paths"

export default defineConfig({
  plugins: [react(), tsconfigPaths()],

  test: {
    globals: true,
    environment: "node",  // Default — override per test with @vitest-environment jsdom
    setupFiles: ["./src/test/setup.ts"],
    include: ["src/**/*.{test,spec}.{ts,tsx}"],
    exclude: ["node_modules", "dist", "e2e/**"],

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

    // Retry flaky tests in CI
    retry: process.env.CI ? 2 : 0,
    // Limit parallel workers
    pool: "threads",
    poolOptions: {
      threads: { maxThreads: 4 },
    },
  },
})
// vitest.workspace.ts — multi-environment workspace
import { defineWorkspace } from "vitest/config"

export default defineWorkspace([
  // Unit tests — Node.js environment
  {
    extends: "./vitest.config.ts",
    test: {
      name: "unit",
      environment: "node",
      include: ["src/**/*.unit.test.ts"],
    },
  },

  // Component tests — JSDOM
  {
    extends: "./vitest.config.ts",
    test: {
      name: "components",
      environment: "jsdom",
      setupFiles: ["./src/test/setup-dom.ts"],
      include: ["src/**/*.test.tsx"],
    },
  },

  // Browser tests — real Chromium via Playwright
  {
    test: {
      name: "browser",
      browser: {
        enabled: true,
        provider: "playwright",
        instances: [{ browser: "chromium" }],
      },
      include: ["src/**/*.browser.test.ts"],
    },
  },
])

Module Mocking

// src/lib/__tests__/order-service.test.ts — vi.mock patterns
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
import { processOrder } from "../order-service"

// Auto-mock entire module — all exports become vi.fn()
vi.mock("../email-service")
vi.mock("../stripe-client")

// Manual factory mock — control implementation
vi.mock("../database", () => ({
  db: {
    orders: {
      create: vi.fn(),
      findById: vi.fn(),
      update: vi.fn(),
    },
    products: {
      findById: vi.fn(),
      update: vi.fn(),
    },
  },
}))

// Import mocked modules for type-safe access
import { db } from "../database"
import { sendOrderConfirmation } from "../email-service"
import { createPaymentIntent } from "../stripe-client"

describe("processOrder", () => {
  beforeEach(() => {
    vi.resetAllMocks()

    // Setup default mock returns
    vi.mocked(db.products.findById).mockResolvedValue({
      id: "prod-1",
      name: "Widget",
      priceCents: 1999,
      stock: 10,
    })
    vi.mocked(db.orders.create).mockResolvedValue({
      id: "ord-abc123",
      status: "pending",
      totalCents: 1999,
      createdAt: new Date().toISOString(),
    })
    vi.mocked(createPaymentIntent).mockResolvedValue({
      id: "pi_test_123",
      client_secret: "pi_test_123_secret",
    })
  })

  it("creates order and sends confirmation", async () => {
    const result = await processOrder({
      customerId: "cust-1",
      items: [{ productId: "prod-1", quantity: 1 }],
    })

    expect(result.orderId).toBe("ord-abc123")

    // Assert db.orders.create was called with correct args
    expect(vi.mocked(db.orders.create)).toHaveBeenCalledWith(
      expect.objectContaining({
        customerId: "cust-1",
        totalCents: 1999,
        status: "pending",
      })
    )

    // Assert email was sent
    expect(vi.mocked(sendOrderConfirmation)).toHaveBeenCalledOnce()
    expect(vi.mocked(sendOrderConfirmation)).toHaveBeenCalledWith(
      expect.objectContaining({ orderId: "ord-abc123" })
    )
  })

  it("throws when product has insufficient stock", async () => {
    vi.mocked(db.products.findById).mockResolvedValue({
      id: "prod-1",
      name: "Widget",
      priceCents: 1999,
      stock: 0,  // Out of stock
    })

    await expect(
      processOrder({ customerId: "cust-1", items: [{ productId: "prod-1", quantity: 1 }] })
    ).rejects.toThrow("Insufficient stock")

    // Ensure no order was created
    expect(vi.mocked(db.orders.create)).not.toHaveBeenCalled()
  })
})

Spies and Timers

// src/lib/__tests__/retry-queue.test.ts — spies + fake timers
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
import { RetryQueue } from "../retry-queue"

describe("RetryQueue", () => {
  beforeEach(() => {
    vi.useFakeTimers()
  })

  afterEach(() => {
    vi.useRealTimers()
  })

  it("retries failed operations with exponential backoff", async () => {
    const operation = vi.fn()
      .mockRejectedValueOnce(new Error("Network error"))
      .mockRejectedValueOnce(new Error("Network error"))
      .mockResolvedValueOnce({ success: true })

    const queue = new RetryQueue({ maxRetries: 3, baseDelayMs: 1000 })
    const resultPromise = queue.enqueue(operation)

    // First attempt fires immediately
    await vi.runAllMicrotasksAsync()
    expect(operation).toHaveBeenCalledTimes(1)

    // Advance 1 second — first retry (1000ms backoff)
    await vi.advanceTimersByTimeAsync(1000)
    expect(operation).toHaveBeenCalledTimes(2)

    // Advance 2 more seconds — second retry (2000ms backoff)
    await vi.advanceTimersByTimeAsync(2000)
    expect(operation).toHaveBeenCalledTimes(3)

    const result = await resultPromise
    expect(result).toEqual({ success: true })
  })

  it("rejects after max retries exhausted", async () => {
    const operation = vi.fn().mockRejectedValue(new Error("Persistent error"))
    const queue = new RetryQueue({ maxRetries: 2, baseDelayMs: 100 })

    const resultPromise = queue.enqueue(operation)

    await vi.runAllTimersAsync()

    await expect(resultPromise).rejects.toThrow("Persistent error")
    expect(operation).toHaveBeenCalledTimes(3)  // 1 initial + 2 retries
  })

  it("uses current Date for timestamp on entry creation", () => {
    vi.setSystemTime(new Date("2024-01-15T10:00:00Z"))

    const queue = new RetryQueue({ maxRetries: 3, baseDelayMs: 1000 })
    const entry = queue.createEntry(() => Promise.resolve())

    expect(entry.createdAt).toEqual(new Date("2024-01-15T10:00:00Z"))
  })
})

Parameterized Tests

// src/lib/__tests__/price-calculator.test.ts — test.each
import { describe, it, expect } from "vitest"
import { calculateDiscount, formatCents, parseOrderStatus } from "../utils"

describe("calculateDiscount", () => {
  // Array form: [input, expected, testName]
  it.each([
    [10000, 10, 9000, "10% off $100"],
    [10000, 0,  10000, "0% off"],
    [10000, 100, 0,    "100% off"],
    [999,   15, 849,   "15% off $9.99 rounds correctly"],
  ])("calculates %s cents with %s%% discount → %s cents (%s)", (
    totalCents,
    discountPercent,
    expected
  ) => {
    expect(calculateDiscount(totalCents, discountPercent)).toBe(expected)
  })
})

describe("formatCents", () => {
  // Tagged template form
  it.each`
    cents    | currency  | expected
    ${0}     | ${"USD"}  | ${"$0.00"}
    ${999}   | ${"USD"}  | ${"$9.99"}
    ${10000} | ${"USD"}  | ${"$100.00"}
    ${1234}  | ${"EUR"}  | ${"€12.34"}
  `("formats $cents $currency → $expected", ({ cents, currency, expected }) => {
    expect(formatCents(cents, currency)).toBe(expected)
  })
})

describe("parseOrderStatus", () => {
  it.each([
    ["PENDING", "pending"],
    ["pending", "pending"],
    ["  Shipped  ", "shipped"],
    ["DELIVERED", "delivered"],
  ])("normalizes %s → %s", (input, expected) => {
    expect(parseOrderStatus(input)).toBe(expected)
  })

  it.each(["unknown", "invalid", ""])(
    "throws for invalid status: %s",
    (status) => {
      expect(() => parseOrderStatus(status)).toThrow("Invalid order status")
    }
  )
})

Snapshot Tests

// src/lib/__tests__/email-template.test.ts — snapshots
import { describe, it, expect } from "vitest"
import { generateOrderEmail } from "../email-template"

describe("generateOrderEmail", () => {
  it("renders order confirmation HTML", () => {
    const html = generateOrderEmail({
      orderId: "ord-test-123",
      customerName: "Alice",
      items: [{ name: "Widget Pro", quantity: 1, priceCents: 2999 }],
      totalCents: 2999,
    })

    // First run creates snapshot, subsequent runs compare
    expect(html).toMatchSnapshot()
  })

  it("renders inline without snapshot file", () => {
    const subject = generateOrderEmail({ orderId: "ord-test", customerName: "Bob" }).subject

    expect(subject).toMatchInlineSnapshot(`"Order ord-test confirmed — thanks Bob!"`)
  })
})

For the Jest alternative when migrating from Jest is not feasible — large existing test suites with Jest-specific matchers like jest.spyOn, JSX transform, or snapshot serializers — the Testing Library guide covers the React component testing patterns that work identically in both. For Playwright E2E testing that complements Vitest unit tests by validating full user flows in real browsers, see the Playwright guide for page objects and browser automation. The Claude Skills 360 bundle includes Vitest skill sets covering mocking strategies, coverage, and workspace configuration. Start with the free tier to try Vitest test generation.

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free