UI はローカライズ済みです。スキルの紹介と説明文書は選択した言語へ AI が自動翻訳し、コード・コマンド・パス・専門用語は原文のままです。原作者の文書が優先されます。
スキルを探す
開発・技術 · SKILL.md

vitest

vitest

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, migra…

Independent GitHub snapshot · Automated classification · Original documentation governs

Tterminalskills / skillsApache-2.0

スキルの説明

GitHub

name: vitest 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

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

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

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

// 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:

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

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."

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:

 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:
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
  1. Run one project and read the full report:
npx vitest run --project unit --reporter=default
 ✓ |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)
  1. 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)
保存したスキル