Skip to main content
skill CX Codex

vitest · jezweb — Claude Code skill: set up Vitest in any project

Detects your project type and writes a working Vitest setup: config, test setup file, utilities and a sample test. Useful when a project has no tests yet.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add jezweb/claude-skills --skill vitest -a claude-code
Open on GitHub

What vitest does

The skill reads package.json, tsconfig.json, wrangler.toml, vite.config.ts and jest.config.* to figure out what it is looking at. Then it classifies the project: Cloudflare Workers, React on Vite, React with SSR, a Node/Hono API, or a library. The type decides the test environment — jsdom for components, node for server code.

From there it generates files instead of advice. You get vitest.config.ts, a src/test/setup.ts file for React, vitest/globals added to tsconfig.json, and test, test:run, test:coverage, test:ui scripts in package.json. Existing scripts are merged, not overwritten. If a vite.config.ts is already there, the test block goes into it, because Vitest reads Vite config natively.

Dependencies are installed with whichever package manager the lock file points to. React projects get jsdom and Testing Library, Workers projects get @cloudflare/vitest-pool-workers. Coming off Jest, the skill also removes packages like ts-jest and babel-jest. One thing to watch: the sample test lands next to the source file, not in a __tests__ folder, so move it if your project follows a different layout.

What it helps with

  • get tests running in a project that has none
  • pick the right test environment for React, Workers or a Node API
  • migrate off Jest and drop the old packages
  • turn on coverage without hand-editing the config
  • get one working test as a pattern to copy

Example prompts for the agent

  • set up Vitest in this project
  • add tests for my Hono API routes
  • migrate the tests from Jest to Vitest

How to install vitest

1Skills CLI (Vercel)

npx skills add jezweb/claude-skills --skill vitest -a claude-code

2Manually: put the files in place

~/.claude/skills/vitest/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/vitest/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about vitest

How do I install vitest in Claude?

Run npx skills add jezweb/claude-skills --skill vitest -a claude-code. The skill file then lives at ~/.claude/skills/vitest/SKILL.md.

What does the vitest skill do?

It detects the project type, generates vitest.config.ts, a setup file, utilities and one sample test, then wires up package scripts and TypeScript types.

Will it replace my existing Vite config?

No. If vite.config.ts exists, the skill adds a test block to it rather than creating a separate file. Package scripts are merged the same way.

Source text

SKILL.md · MIT
---
name: vitest
description: "Set up Vitest testing in any project — detects type (Cloudflare Workers, React, Node, library), generates vitest.config.ts, test setup, utilities, and a sample test. Covers mocking patterns, coverage config, workspace setup, Jest migration. Use whenever the user mentions adding tests, setting up Vitest, configuring tests, migrating from Jest, fixing testing infrastructure, or asks 'how do I test this'."
license: MIT
---

# Vitest Setup

Detect the project type, generate the right Vitest configuration, and produce working test infrastructure. Not a reference card — this skill creates files.

## Workflow

1. **Detect** — scan the project to determine type and existing setup
2. **Configure** — generate vitest.config.ts tailored to the environment
3. **Scaffold** — create test setup, utilities, and a sample test
4. **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 | `node` with Workers-specific setup |
| **React (Vite)** | @vitejs/plugin-react, react-dom | `jsdom` or `happy-dom` |
| **React (SSR/TanStack Start)** | @tanstack/start, vinxi | Split: `node` for server, `jsdom` for client |
| **Node/Hono API** | hono, express, no react-dom | `node` |
| **Library** | exports field, no framework deps | `node` |

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:

```bash
# 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

```typescript
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:

```typescript
/// <reference types="vitest/config" />
import { defineConfig } from "vite";
import { cloudflare } from "@cloudflare/vite-plugin";

export default defineConfig({
  plugins: [cloudflare()],
  test: {
    globals: true,
  },
});
```

### React (Vite)

```typescript
/// <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

```typescript
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    globals: true,
    environment: "node",
  },
});
```

### With Coverage (add to any config)

```typescript
  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):

```typescript
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:

```json
{
  "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

```json
{
  "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`):

```typescript
import { 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`):

```typescript
import { 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`):

```typescript
import { 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:

```bash
pnpm test:run
```

If it fails, diagnose and fix. Common issues:

| Error | Fix |
|-------|-----|
| `Cannot find module 'vitest'` | Check install completed, check `node_modules/.vitest` exists |
| `ReferenceError: describe is not defined` | Add `globals: true` to config, or add `types: ["vitest/globals"]` to tsconfig |
| `document is not defined` | Wrong environment — set `environment: "jsdom"` for React tests |
| `Cannot use import.meta` | Ensure vitest.config uses `.ts` extension and project has `"type": "module"` or Vite handles transforms |
| Workers bindings undefined | Use `@cloudflare/vitest-pool-workers` 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)

```typescript
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)

```typescript
it("spies on console.warn", () => {
  const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
  doSomethingThatWarns();
  expect(spy).toHaveBeenCalledOnce();
  spy.mockRestore();
});
```

### Fake timers

```typescript
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

```typescript
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

```typescript
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

```typescript
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):

1. **Generate the vitest.config.ts** using the steps above
2. **Update imports** in existing test files:

```typescript
// 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");
```

3. **Remove Jest packages:**
```bash
pnpm remove jest ts-jest @types/jest jest-environment-jsdom babel-jest @jest/globals
```

4. **Update tsconfig** — replace `"types": ["jest"]` with `"types": ["vitest/globals"]`

5. **Run tests** and fix any remaining issues

Key replacements:

| Jest | Vitest |
|------|--------|
| `jest.fn()` | `vi.fn()` |
| `jest.mock()` | `vi.mock()` |
| `jest.spyOn()` | `vi.spyOn()` |
| `jest.useFakeTimers()` | `vi.useFakeTimers()` |
| `jest.clearAllMocks()` | `vi.clearAllMocks()` |
| `jest.requireActual()` | `vi.importActual()` |
| `@jest/globals` | `vitest` |
| `jest.config.js` | `vitest.config.ts` |

---

## Workspace Setup (Monorepos)

For monorepo projects with multiple packages:

```typescript
// 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:
- `vitest.config.ts` (or test block added to existing vite.config.ts)
- `src/test/setup.ts` (React projects)
- Updated `tsconfig.json` with vitest/globals type
- Updated `package.json` with test scripts
- 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.

© jezweb · MIT · jezweb/claude-skills

Similar skills

Everything for Testing and QA →

vitest by other authors comes first.

vitest Anthony Fu This skill guides work with Vitest tests, mocks, coverage, filtering, and fixtures. Use it when setting up tests or checking how Vitest fits a Vite project. CX Codex +1 43,800installs vitest pproenca Equips your coding agent with 44 prioritized rules for writing, debugging, and speeding up Vitest suites. Covers async patterns, mocks, and test isolation. CX Codex +1 6,100installs vitest-skill TestMu AI Generates Vitest unit tests for JavaScript and TypeScript projects. Provides patterns for vi mocking and React component testing. CX Codex +1 448installs vitest-testing existential-birds A skill that hands your coding agent a Vitest reference: assertions, mocks, async matchers, config. Useful when the project already runs on Vitest. CX Codex +1 467installs cloudflare-workers-testing secondsky A skill for writing Cloudflare Workers tests with Vitest and real D1, KV, R2 and Durable Objects bindings. Useful when tests break on mocks or can't see the environment. CX Codex +1 325installs vitest-midscene-e2e web-infra-dev Connects Vitest with Midscene to build AI-driven UI tests across Web, Android, and iOS using natural language. It scaffolds projects and runs tests without fragile element selectors. CX Codex +1 2,100installs vitest-unit-testing-cursorrules-prompt-file PatrickJS A Cursor rule that turns the agent into a Vitest and TypeScript test writer: it targets business logic and utilities, mocking dependencies with vi.mock. ★ 41,011repo stars react-testing affaan-m A skill for writing React component, hook and page tests with React Testing Library, Vitest or Jest, MSW network mocks and axe accessibility assertions. CX Codex +1 4,800installs

From the same set54

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor