5,487,579 AI resources indexedWorking toward the world’s largest AI resource index.

PDF

Vitest — Blazing Fast Unit Testing

Facilitates unit and integration testing with Vitest, offering a Jest-compatible API and faster execution for modern web applications.

Installable skillApache-2.0By terminal-skillsUpdated Nov 1, 2026
INTENDED FORClaude CodeCodexOpenClawCursorInstall in a listed agent.

instructions.md

---
name: vitest-20a398530e53
description: >-
  Vitest is a test runner for JavaScript and TypeScript built on Vite, with a
  Jest-compatible API, built-in mocking, snapshots and code coverage. Use when
  a user asks to set up or run unit tests in a Vite or Node project, write
  tests with describe/it/expect, mock modules with vi.mock, add coverage
  thresholds, configure vitest.config.ts or test projects, migrate from Jest,
  or upgrade to Vitest 5.
license: Apache-2.0
compatibility: "Node.js 22.12+ and Vite 6.4+ (Vitest 5)"
metadata:
  author: terminal-skills
  version: 1.1.0
  category: development
  repository: https://github.com/vitest-dev/vitest
  tags: "testing, vite, typescript, mocking, coverage"
---

# Vitest — Blazing Fast Unit Testing

## Overview

Vitest is the Vite-native testing framework. It runs unit, integration and component tests with native TypeScript support, a Jest-compatible API, built-in mocking, code coverage, snapshot testing and watch mode — reusing Vite's transform pipeline, so tests need no separate compilation step. This skill targets Vitest 5 (released September 2026), which changed several defaults; the Guidelines list what to check when upgrading.

## Instructions

### Installation

```bash
npm install -D vitest
npm install -D @vitest/coverage-v8         # Coverage
npm install -D @vitest/ui                  # Browser UI
npm install -D jsdom                       # Only for environment: "jsdom"
```

Vitest 5 needs Node.js 22.12+ and Vite 6.4+. Vite is a peer dependency: npm, pnpm and Bun install it automatically, Yarn needs `yarn add -D vitest vite`. Add `.vitest/` (reports and artifacts) and `coverage/` to `.gitignore`.

### Tests

```typescript
// src/pricing.test.ts
import { describe, it, expect } from "vitest";
import { calculateDiscount, formatPrice } from "./pricing";

describe("calculateDiscount", () => {
  it("applies percentage discount", () => {
    expect(calculateDiscount(100, 20)).toBe(80);
  });

  it("never goes below zero", () => {
    expect(calculateDiscount(10, 200)).toBe(0);
  });

  it.each([
    { price: 100, discount: 10, expected: 90 },
    { price: 50, discount: 50, expected: 25 },
    { price: 200, discount: 0, expected: 200 },
  ])("$price with $discount% = $expected", ({ price, discount, expected }) => {
    expect(calculateDiscount(price, discount)).toBe(expected);
  });
});

describe("formatPrice", () => {
  it("formats with currency symbol", () => {
    expect(formatPrice(29.99, "USD")).toBe("$29.99");
    expect(formatPrice(29.99, "EUR")).toBe("€29.99");
  });
});
```

Test files must contain `.test.` or `.spec.` in their name (default `include`: `**/*.{test,spec}.?(c|m)[jt]s?(x)`).

### Mocking

```typescript
// src/orders.test.ts
import { describe, it, expect, vi } from "vitest";
import { processOrder } from "./orders";
import { sendEmail } from "./email";
import { chargeCard } from "./payments";

// vi.mock is hoisted above the imports — keep it at the top level of the file
vi.mock("./email", () => ({
  sendEmail: vi.fn().mockResolvedValue({ success: true }),
}));

vi.mock("./payments", () => ({
  chargeCard: vi.fn().mockResolvedValue({ chargeId: "ch_3PqXk2" }),
}));

describe("processOrder", () => {
  // No beforeEach(vi.clearAllMocks) needed: Vitest 5 clears call history before every test

  it("charges card and sends confirmation email", async () => {
    const order = { userId: "u_481", items: [{ id: "sku_114", qty: 2 }], total: 59.98 };
    const result = await processOrder(order);

    expect(chargeCard).toHaveBeenCalledWith({ amount: 59.98, userId: "u_481" });
    expect(sendEmail).toHaveBeenCalledWith(
      expect.objectContaining({ type: "order_confirmation", userId: "u_481" }),
    );
    expect(result.status).toBe("completed");
  });

  it("stops on payment failure", async () => {
    vi.mocked(chargeCard).mockRejectedValueOnce(new Error("Card declined"));

    await expect(processOrder({ userId: "u_481", items: [], total: 0 }))
      .rejects.toThrow("Card declined");
    expect(sendEmail).not.toHaveBeenCalled();
  });

  it("spies on a method", () => {
    const spy = vi.spyOn(console, "log").mockImplementation(() => {});
    console.log("order shipped");
    expect(spy).toHaveBeenCalledWith("order shipped");
  });

  it("controls timers", () => {
    vi.useFakeTimers();
    const callback = vi.fn();
    setTimeout(callback, 5000);
    vi.advanceTimersByTime(5000);
    expect(callback).toHaveBeenCalled();
    vi.useRealTimers();
  });
});
```

To keep the real implementation and only record calls, pass `{ spy: true }` instead of a factory: `vi.mock(import("./payments"), { spy: true })`.

### Configuration

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

export default defineConfig({
  test: {
    globals: true,                         // No need to import describe/it/expect
    environment: "node",                   // Or "jsdom" / "happy-dom" for browser APIs
    coverage: {
      provider: "v8",
      reporter: ["text", "html", "lcov"],
      include: ["src/**/*.ts"],            // Also report files no test imports
      thresholds: { lines: 80, branches: 75, functions: 80 },
    },
    include: ["**/*.{test,spec}.{ts,tsx}"],
    setupFiles: ["./test/setup.ts"],       // The file must exist; drop the line if there is none
  },
});
```

Vitest also reads `test` from an existing `vite.config.ts`. With `globals: true`, add `"types": ["vitest/globals"]` to `compilerOptions` in `tsconfig.json`. A single file can switch environment with a `// @vitest-environment jsdom` comment at the top of the file.

Several configurations in one run (monorepo packages, node + DOM tests) are declared as `projects` in the root config:

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

export default defineConfig({
  test: {
    coverage: { provider: "v8", include: ["src/**/*.ts"] },   // root-only option
    projects: [
      { test: { name: "unit", include: ["src/**/*.test.ts"], environment: "node" } },
      { test: { name: "dom", include: ["src/**/*.dom.test.tsx"], environment: "jsdom" } },
      "packages/*",                        // every folder in packages/ is a project
    ],
  },
});
```

### Running

```bash
npx vitest                                 # Watch mode; single run in CI or without a TTY
npx vitest run                             # Single run (CI)
npx vitest run --coverage                  # With coverage, fails below thresholds
npx vitest --ui                            # Browser UI — open the printed URL, it carries a token
npx vitest run src/pricing.test.ts         # Files whose path contains the filter
npx vitest run -t "applies percentage"     # Tests whose full name matches
npx vitest run --project unit              # One project
npx vitest related src/pricing.ts --run    # Tests that import the given source files
npx vitest run --changed                   # Tests affected by uncommitted changes
npx vitest run --reporter=junit            # Writes .vitest/junit/output.xml
```

## Examples

### Example 1: Add tests and a coverage gate to a TypeScript project

User: "Set up Vitest for our pricing module and make CI fail under 80% line coverage."

```bash
npm install -D vitest @vitest/coverage-v8
npm pkg set scripts.test="vitest" scripts.test:ci="vitest run --coverage"
npm run test:ci
```

With the two test files and the first configuration above (and an existing `test/setup.ts`), the mocked `email.ts` and `payments.ts` never execute, so the totals fall under the threshold and the command exits with code 1:

```text
 Test Files  2 passed (2)
      Tests  10 passed (10)

 % Coverage report from v8
-------------|---------|----------|---------|---------|-------------------
File         | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files    |   71.42 |      100 |      60 |   71.42 |
 email.ts    |       0 |      100 |       0 |       0 | 1
 payments.ts |       0 |      100 |       0 |       0 | 1
-------------|---------|----------|---------|---------|-------------------
ERROR: Coverage for lines (71.42%) does not meet global threshold (80%)
ERROR: Coverage for functions (60%) does not meet global threshold (80%)
```

The HTML report is written to `coverage/index.html`.

### Example 2: Upgrade a monorepo from Vitest 3 to Vitest 5

User: "After bumping vitest to 5 our workspace tests are gone and one file crashes with a vi.mock error."

1. `vitest.workspace.ts` is no longer read, and a `test.workspace` key throws "The `test.workspace` option was removed in Vitest 4". Move its entries into `test.projects` (see Configuration) and delete the file.
2. Move every `vi.mock` / `vi.hoisted` call out of `describe` and `test` callbacks to the top level of the file. Vitest 5 fails the file otherwise:

```text
Error: 1 call in "src/orders.test.ts" was defined outside of the module's top level scope:

- vi.mock("./email") at src/orders.test.ts:2:28
```

3. Run one project and read the full report:

```bash
npx vitest run --project unit --reporter=default
```

```text
 ✓ |unit| src/orders.test.ts (4 tests) 4ms
 ✓ |unit| src/pricing.test.ts (6 tests) 10ms

 Test Files  2 passed (2)
      Tests  10 passed (10)
```

4. Assertions that counted calls made in an earlier test or in `beforeAll` now see zero calls (`clearMocks` is on). Fix the test, or set `test.clearMocks: false` to restore the old behavior.

## Guidelines

1. **Vite-powered** — Uses Vite's transform; TypeScript, JSX, ESM work without config; instant re-runs in watch mode
2. **Jest-compatible** — Same `describe`/`it`/`expect` API; replace `jest.fn`/`jest.mock` with `vi.fn`/`vi.mock` when migrating
3. **Native TypeScript** — No ts-jest, no babel; Vite handles transforms. Tests run without type checking, so keep `tsc --noEmit` in CI
4. **vi.mock() is hoisted** — It runs before imports, so a factory cannot use variables declared below it; create those with `vi.hoisted()`. Use `vi.doMock()` when a mock must be registered later, inside a test
5. **Mock history resets by itself** — Since Vitest 5 `clearMocks` defaults to `true`: call counts are cleared before each test, implementations stay. `mockReset` and `restoreMocks` are still opt-in
6. **In-source testing** — Tests inside `if (import.meta.vitest)` blocks run once `test.includeSource` lists the files; add `define: { "import.meta.vitest": "undefined" }` to the build config so bundlers drop them from production
7. **Projects, not workspaces** — `test.projects` replaced `vitest.workspace.ts`. Inline projects inherit the root config by default in Vitest 5 (`extends: false` opts out); `coverage` and `reporters` can only be set at the root
8. **Coverage counts imported files only** — Set `coverage.include` to see untested files. In Vitest 5 a pattern without wildcards (`"src"`) is treated as a directory, and glob thresholds need their own `perFile`
9. **Reports go to `.vitest/`** — The `json` and `junit` reporters write `.vitest/json/output.json` and `.vitest/junit/output.xml` instead of stdout unless `outputFile` is set
10. **Quiet output under AI agents** — When Vitest detects a coding agent it switches to the `minimal` reporter, which prints only failures and the totals. Pass `--reporter=default` (or `verbose`) to see every file
11. **Config lookup** — Vitest 5 no longer searches parent directories for a config file; run from the project root or pass `--config ../vitest.config.ts`
12. **Not an end-to-end tool** — For full user flows across pages use Playwright or Cypress. Component tests that need a real browser can use Browser Mode (`npx vitest init browser`)

Discuss this resource

Platform guidance

Imported from the listed AgentSkill.sh category. Complete supplied package files and notices are retained. Required external tools and credentials are not installed. No bundled code was executed during import. Individual agent task behavior has not been tested.