Claude-skills vitest
Set up Vitest testing in any project — detects project type (Cloudflare Workers, React, Node), generates vitest.config.ts, test setup, utilities, and sample tests. Also covers mocking patterns, coverage config, workspace setup, and Jest migration. Trigger with 'set up vitest', 'add tests', 'configure vitest', 'migrate from jest', 'vitest config', 'add unit tests', or 'testing setup'.
git clone https://github.com/jezweb/claude-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/jezweb/claude-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/dev-tools/skills/vitest" ~/.claude/skills/jezweb-claude-skills-vitest && rm -rf "$T"
plugins/dev-tools/skills/vitest/SKILL.mdVitest Setup
Detect the project type, generate the right Vitest configuration, and produce working test infrastructure. Not a reference card — this skill creates files.
Workflow
- Detect — scan the project to determine type and existing setup
- Configure — generate vitest.config.ts tailored to the environment
- Scaffold — create test setup, utilities, and a sample test
- Wire up — add package.json scripts and TypeScript config
Step 1: Detect Project Type
Read these files to determine the project:
package.json → dependencies, scripts, type field tsconfig.json → paths, compiler options wrangler.toml → Cloudflare Workers project vite.config.ts → existing Vite setup (extend, don't replace) vitest.config.ts → already configured? just fill gaps jest.config.* → migration candidate src/ → source structure
Classify as one of:
| Type | Signals | Environment |
|---|---|---|
| Cloudflare Workers | wrangler.toml, @cloudflare/workers-types, cloudflare vite plugin | with Workers-specific setup |
| React (Vite) | @vitejs/plugin-react, react-dom | or |
| React (SSR/TanStack Start) | @tanstack/start, vinxi | Split: for server, for client |
| Node/Hono API | hono, express, no react-dom | |
| Library | exports field, no framework deps | |
If a
vite.config.ts already exists, extend it rather than creating a separate vitest.config.ts — Vitest reads Vite config natively.
Step 2: Install Dependencies
Generate the install command based on detected type:
# Base (always) pnpm add -D vitest # React projects — add jsdom and Testing Library pnpm add -D @vitest/coverage-v8 jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event # Workers projects — add Cloudflare test utilities pnpm add -D @vitest/coverage-v8 @cloudflare/vitest-pool-workers # Node/Hono projects pnpm add -D @vitest/coverage-v8 # If migrating from Jest, also remove: pnpm remove jest ts-jest @types/jest jest-environment-jsdom babel-jest
Use the project's package manager (check for pnpm-lock.yaml, yarn.lock, bun.lockb, or package-lock.json).
Step 3: Generate vitest.config.ts
Cloudflare Workers
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; export default defineWorkersConfig({ test: { globals: true, poolOptions: { workers: { wrangler: { configPath: "./wrangler.toml" }, }, }, }, });
If the project uses the Cloudflare Vite plugin (
@cloudflare/vite-plugin), integrate into the existing vite.config.ts instead:
/// <reference types="vitest/config" /> import { defineConfig } from "vite"; import { cloudflare } from "@cloudflare/vite-plugin"; export default defineConfig({ plugins: [cloudflare()], test: { globals: true, }, });
React (Vite)
/// <reference types="vitest/config" /> import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [react()], test: { globals: true, environment: "jsdom", setupFiles: ["./src/test/setup.ts"], css: true, }, });
If a vite.config.ts already exists, add the
test block to it rather than creating a new file.
Node / Hono API
import { defineConfig } from "vitest/config"; export default defineConfig({ test: { globals: true, environment: "node", }, });
With Coverage (add to any config)
test: { // ... existing config coverage: { provider: "v8", reporter: ["text", "html", "lcov"], exclude: [ "node_modules/", "**/*.config.*", "**/*.d.ts", "**/test/**", ], }, },
Step 4: Generate Test Setup File
Create
src/test/setup.ts (React projects only):
import "@testing-library/jest-dom/vitest";
That single import adds all the custom matchers (toBeInTheDocument, toHaveTextContent, etc.) and registers the Vitest
expect.extend automatically.
Step 5: Add TypeScript Config
Add to
tsconfig.json compilerOptions:
{ "compilerOptions": { "types": ["vitest/globals"] } }
For projects with multiple tsconfig files (e.g. tsconfig.app.json + tsconfig.node.json), add to the one that covers test files — usually the root tsconfig.json or create a tsconfig.test.json that extends it.
Step 6: Add Package.json Scripts
{ "scripts": { "test": "vitest", "test:run": "vitest run", "test:coverage": "vitest run --coverage", "test:ui": "vitest --ui" } }
Don't overwrite existing scripts — merge with what's there.
Step 7: Generate Sample Test
Write one test file that demonstrates the right patterns for this specific project. Place it next to real source code, not in a separate
__tests__ directory.
For a Hono API route (e.g. src/routes/health.ts
):
src/routes/health.tsimport { describe, it, expect } from "vitest"; import { app } from "../index"; describe("GET /health", () => { it("returns 200 with status ok", async () => { const res = await app.request("/health"); expect(res.status).toBe(200); const body = await res.json(); expect(body).toEqual({ status: "ok" }); }); });
For a React component (e.g. src/components/Button.tsx
):
src/components/Button.tsximport { describe, it, expect } from "vitest"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { Button } from "./Button"; describe("Button", () => { it("renders with label", () => { render(<Button>Click me</Button>); expect(screen.getByRole("button", { name: /click me/i })).toBeInTheDocument(); }); it("calls onClick when clicked", async () => { const user = userEvent.setup(); const handleClick = vi.fn(); render(<Button onClick={handleClick}>Click me</Button>); await user.click(screen.getByRole("button")); expect(handleClick).toHaveBeenCalledOnce(); }); });
For a utility function (e.g. src/utils/format.ts
):
src/utils/format.tsimport { describe, it, expect } from "vitest"; import { formatCurrency } from "./format"; describe("formatCurrency", () => { it("formats whole numbers", () => { expect(formatCurrency(1000)).toBe("$1,000.00"); }); it("formats decimals", () => { expect(formatCurrency(49.9)).toBe("$49.90"); }); it("handles zero", () => { expect(formatCurrency(0)).toBe("$0.00"); }); });
Pick a real file from the project to test. Don't invent a fake module — the sample test should run immediately after setup.
Step 8: Verify
Run the tests to confirm everything works:
pnpm test:run
If it fails, diagnose and fix. Common issues:
| Error | Fix |
|---|---|
| Check install completed, check exists |
| Add to config, or add to tsconfig |
| Wrong environment — set for React tests |
| Ensure vitest.config uses extension and project has or Vite handles transforms |
| Workers bindings undefined | Use instead of plain vitest, check wrangler.toml path |
Mocking Reference
These patterns are for writing tests after setup is complete. Include them in the sample test or a
src/test/examples.test.ts if the user asks for mocking examples.
Module mocking (vi.mock)
import { vi, describe, it, expect } from "vitest"; import { getUser } from "./api"; vi.mock("./api", () => ({ getUser: vi.fn(), })); it("mocks a module function", async () => { vi.mocked(getUser).mockResolvedValue({ id: 1, name: "Test" }); const user = await getUser(1); expect(user.name).toBe("Test"); expect(getUser).toHaveBeenCalledWith(1); });
Spy on methods (vi.spyOn)
it("spies on console.warn", () => { const spy = vi.spyOn(console, "warn").mockImplementation(() => {}); doSomethingThatWarns(); expect(spy).toHaveBeenCalledOnce(); spy.mockRestore(); });
Fake timers
import { vi, beforeEach, afterEach, it, expect } from "vitest"; beforeEach(() => { vi.useFakeTimers(); vi.setSystemTime(new Date("2026-01-15T10:00:00Z")); }); afterEach(() => { vi.useRealTimers(); }); it("uses controlled time", () => { expect(new Date().toISOString()).toBe("2026-01-15T10:00:00.000Z"); });
Global stubs
it("stubs fetch", async () => { const mockFetch = vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ data: "test" }), }); vi.stubGlobal("fetch", mockFetch); const res = await fetch("/api/data"); expect(mockFetch).toHaveBeenCalledWith("/api/data"); vi.unstubAllGlobals(); });
Snapshot testing
it("matches snapshot", () => { const result = generateConfig({ debug: true }); expect(result).toMatchSnapshot(); }); it("matches inline snapshot", () => { expect({ status: "ok", count: 3 }).toMatchInlineSnapshot(` { "count": 3, "status": "ok", } `); });
Parameterized tests
describe.each([ { input: "hello", expected: "HELLO" }, { input: "world", expected: "WORLD" }, { input: "", expected: "" }, ])("toUpperCase($input)", ({ input, expected }) => { it(`returns ${expected}`, () => { expect(input.toUpperCase()).toBe(expected); }); });
Jest Migration
When the detected project has Jest (
jest.config.*, @types/jest, ts-jest in dependencies):
- Generate the vitest.config.ts using the steps above
- Update imports in existing test files:
// Before import { jest } from "@jest/globals"; jest.mock("./api"); jest.fn(); jest.spyOn(obj, "method"); // After import { vi } from "vitest"; vi.mock("./api"); vi.fn(); vi.spyOn(obj, "method");
- Remove Jest packages:
pnpm remove jest ts-jest @types/jest jest-environment-jsdom babel-jest @jest/globals
-
Update tsconfig — replace
with"types": ["jest"]"types": ["vitest/globals"] -
Run tests and fix any remaining issues
Key replacements:
| Jest | Vitest |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
Workspace Setup (Monorepos)
For monorepo projects with multiple packages:
// vitest.workspace.ts import { defineWorkspace } from "vitest/config"; export default defineWorkspace([ "packages/*/vitest.config.ts", ]);
Each package gets its own config. The workspace file just points to them.
What This Skill Produces
After running, the project should have:
(or test block added to existing vite.config.ts)vitest.config.ts
(React projects)src/test/setup.ts- Updated
with vitest/globals typetsconfig.json - Updated
with test scriptspackage.json - At least one passing sample test against real source code
- Dependencies installed
The tests should pass on first run. If they don't, fix them before finishing.