vitest
Vitest testing framework: Vite-powered tests, Jest-compatible API, mocking, snapshots, coverage, browser mode, and TypeScript support.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install itechmeat-llm-code-vitest
Repository
Skill path: skills/vitest
Vitest testing framework: Vite-powered tests, Jest-compatible API, mocking, snapshots, coverage, browser mode, and TypeScript support.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack, Backend, Testing.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: itechmeat.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install vitest into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/itechmeat/llm-code before adding vitest to shared team environments
- Use vitest for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: vitest
description: "Vitest testing framework: Vite-powered tests, Jest-compatible API, mocking, snapshots, coverage, browser mode, and TypeScript support."
version: "4.0.18"
release_date: "2026-01-21"
---
# Vitest
Next generation testing framework powered by Vite.
## Quick Navigation
- [Test API](references/api.md) - test, describe, hooks
- [Expect API](references/expect.md) - matchers and assertions
- [Mocking](references/mocking.md) - vi.fn, vi.mock, fake timers
- [Configuration](references/config.md) - vitest.config.ts options
- [CLI](references/cli.md) - command line reference
- [Browser Mode](references/browser.md) - real browser testing
## When to Use
- Testing Vite-based applications (shared config)
- Need Jest-compatible API with native ESM support
- Component testing in real browser
- Fast watch mode with HMR
- TypeScript testing without extra config
- Parallel test execution
## Installation
```bash
npm install -D vitest
```
**Requirements:** Vite >=v6.0.0, Node >=v20.0.0
## Quick Start
```js
// sum.js
export function sum(a, b) {
return a + b;
}
```
```js
// sum.test.js
import { expect, test } from "vitest";
import { sum } from "./sum.js";
test("adds 1 + 2 to equal 3", () => {
expect(sum(1, 2)).toBe(3);
});
```
```json
// package.json
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"coverage": "vitest run --coverage"
}
}
```
## Configuration
```ts
// vitest.config.ts (recommended)
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true, // Enable global test APIs
environment: "jsdom", // Browser-like environment
include: ["**/*.{test,spec}.{js,ts,jsx,tsx}"],
coverage: {
provider: "v8",
reporter: ["text", "html"],
},
},
});
```
Or extend Vite config:
```ts
// vite.config.ts
/// <reference types="vitest/config" />
import { defineConfig } from "vite";
export default defineConfig({
test: {
// test options
},
});
```
## Test File Naming
By default, tests must contain `.test.` or `.spec.` in filename:
- `sum.test.js`
- `sum.spec.ts`
- `__tests__/sum.js`
## Key Commands
```bash
# Watch mode (default)
vitest
# Single run
vitest run
# With coverage
vitest run --coverage
# Filter by file/test name
vitest sum
vitest -t "should add"
# UI mode
vitest --ui
# Browser tests
vitest --browser.enabled
```
## Common Patterns
### Basic Test
```ts
import { describe, it, expect, beforeEach } from "vitest";
describe("Calculator", () => {
let calc: Calculator;
beforeEach(() => {
calc = new Calculator();
});
it("adds numbers", () => {
expect(calc.add(1, 2)).toBe(3);
});
it("throws on invalid input", () => {
expect(() => calc.add("a", 1)).toThrow();
});
});
```
### Mocking
```ts
import { vi, expect, test } from "vitest";
import { fetchUser } from "./api";
vi.mock("./api", () => ({
fetchUser: vi.fn(),
}));
test("uses mocked API", async () => {
vi.mocked(fetchUser).mockResolvedValue({ name: "John" });
const user = await fetchUser(1);
expect(fetchUser).toHaveBeenCalledWith(1);
expect(user.name).toBe("John");
});
```
### Snapshot Testing
```ts
import { expect, test } from "vitest";
test("matches snapshot", () => {
const result = generateConfig();
expect(result).toMatchSnapshot();
});
// Inline snapshot (auto-updates)
test("inline snapshot", () => {
expect({ foo: "bar" }).toMatchInlineSnapshot();
});
```
### Async Testing
```ts
import { expect, test } from "vitest";
test("async/await", async () => {
const result = await fetchData();
expect(result).toBeDefined();
});
test("resolves", async () => {
await expect(Promise.resolve("ok")).resolves.toBe("ok");
});
test("rejects", async () => {
await expect(Promise.reject(new Error())).rejects.toThrow();
});
```
### Fake Timers
```ts
import { vi, expect, test, beforeEach, afterEach } from "vitest";
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
test("advances time", () => {
const callback = vi.fn();
setTimeout(callback, 1000);
vi.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalled();
});
```
## Jest Migration
Most Jest code works with minimal changes:
```diff
- import { jest } from '@jest/globals'
+ import { vi } from 'vitest'
- jest.fn()
+ vi.fn()
- jest.mock('./module')
+ vi.mock('./module')
- jest.useFakeTimers()
+ vi.useFakeTimers()
```
**Key differences:**
- Use `vi` instead of `jest`
- Globals not enabled by default (add `globals: true`)
- `vi.mock` is hoisted (use `vi.doMock` for non-hoisted)
- No `jest.requireActual` (use `vi.importActual`)
## Environment Selection
```ts
// vitest.config.ts
{
test: {
environment: 'jsdom', // or 'happy-dom', 'node', 'edge-runtime'
}
}
// Per-file (docblock at top)
/** @vitest-environment jsdom */
```
## TypeScript
```json
// tsconfig.json
{
"compilerOptions": {
"types": ["vitest/globals"]
}
}
```
## References
See `references/` directory for detailed documentation on:
- Test API and hooks
- All expect matchers
- Mocking functions and modules
- Configuration options
- CLI commands
- Browser mode testing
## Links
- [Documentation](https://vitest.dev/)
- [API Reference](https://vitest.dev/api/)
- [GitHub](https://github.com/vitest-dev/vitest)
- [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=vitest.explorer)
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### references/api.md
```markdown
# Vitest Test API Reference
Core testing functions and hooks.
## Test Functions
### test / it
```ts
import { expect, test } from "vitest";
test("should work", () => {
expect(Math.sqrt(4)).toBe(2);
});
// With timeout (default 5s, configurable via testTimeout)
test("async operation", async () => {
// ...
}, 10000);
// With options object
test("with options", { timeout: 10000, retry: 2 }, () => {
// ...
});
```
### test.skip / test.only / test.todo
```ts
test.skip("skipped test", () => {});
test.only("only this runs", () => {});
test.todo("implement later");
// Dynamic skip
test("conditional", (context) => {
context.skip(condition, "optional reason");
});
```
### test.skipIf / test.runIf
```ts
const isDev = process.env.NODE_ENV === "development";
test.skipIf(isDev)("prod only", () => {});
test.runIf(isDev)("dev only", () => {});
```
### test.concurrent / test.sequential
```ts
// Run tests in parallel
test.concurrent("concurrent 1", async () => {});
test.concurrent("concurrent 2", async () => {});
// Force sequential in concurrent context
test.sequential("must run alone", async () => {});
```
### test.each / test.for
```ts
// Parameterized tests
test.each([
[1, 1, 2],
[1, 2, 3],
])("add(%i, %i) -> %i", (a, b, expected) => {
expect(a + b).toBe(expected);
});
// With object parameters
test.each([
{ a: 1, b: 1, expected: 2 },
{ a: 1, b: 2, expected: 3 },
])("add($a, $b) -> $expected", ({ a, b, expected }) => {
expect(a + b).toBe(expected);
});
// test.for - keeps array intact (recommended)
test.for([
[1, 1, 2],
[1, 2, 3],
])("add(%i, %i) -> %i", ([a, b, expected]) => {
expect(a + b).toBe(expected);
});
```
### test.extend (Custom Fixtures)
```ts
const myTest = test.extend({
todos: async ({ task }, use) => {
const todos = [1, 2, 3];
await use(todos);
// cleanup
},
});
myTest("with fixture", ({ todos }) => {
expect(todos.length).toBe(3);
});
```
## describe (Suites)
```ts
describe("group", () => {
test("test 1", () => {});
test("test 2", () => {});
});
// Nested
describe("outer", () => {
describe("inner", () => {
test("nested test", () => {});
});
});
```
### describe modifiers
```ts
describe.skip('skipped suite', () => {})
describe.only('only this suite', () => {})
describe.todo('implement later')
describe.concurrent('parallel tests', () => {})
describe.sequential('sequential tests', () => {})
describe.shuffle('random order', () => {})
describe.each([...])('parameterized', (params) => {})
```
## Setup and Teardown
```ts
import { beforeAll, afterAll, beforeEach, afterEach } from "vitest";
beforeAll(async () => {
// once before all tests
await setupDatabase();
// cleanup function (equivalent to afterAll)
return async () => {
await teardownDatabase();
};
});
afterAll(async () => {
// once after all tests
});
beforeEach(async () => {
// before each test
});
afterEach(async () => {
// after each test
});
```
## Test Hooks (Inside Test)
```ts
import { test, onTestFinished, onTestFailed } from "vitest";
test("with cleanup", () => {
const db = connectDb();
onTestFinished(() => db.close());
onTestFailed(({ task }) => {
console.log("Failed:", task.result.errors);
});
db.query("SELECT * FROM users");
});
// For concurrent tests, use context
test.concurrent("concurrent", ({ onTestFinished }) => {
onTestFinished(() => cleanup());
});
```
## Benchmark
```ts
import { bench, describe } from "vitest";
bench(
"normal sorting",
() => {
const x = [1, 5, 4, 2, 3];
x.sort((a, b) => a - b);
},
{ time: 1000, iterations: 100 }
);
describe("benchmarks", () => {
bench.skip("skipped", () => {});
bench.only("only this", () => {});
bench.todo("implement later");
});
```
## Test Options
```ts
interface TestOptions {
timeout?: number; // max execution time (default 5s)
retry?: number; // retry count on failure (default 0)
repeats?: number; // repeat count even on success
}
```
## Important Notes
- **Concurrent tests**: Use `expect` from context for snapshots
```ts
test.concurrent("test", async ({ expect }) => {
expect(foo).toMatchSnapshot();
});
```
- **Type checker mode**: `.concurrent`, `.skipIf`, `.runIf`, `.each`, `.fails` not supported
- **Default timeout**: 5 seconds (configurable via `testTimeout` config)
```
### references/expect.md
```markdown
# Vitest Expect API Reference
Assertion matchers and utilities.
## Basic Matchers
```ts
import { expect } from "vitest";
// Identity (Object.is)
expect(value).toBe(expected);
expect(obj).not.toBe(otherObj); // Different references
// Deep equality
expect(obj).toEqual({ a: 1, b: { c: 2 } });
// Strict equality (respects types and object properties)
expect({ a: undefined }).not.toStrictEqual({}); // undefined !== missing
expect(new MyClass()).not.toStrictEqual({ prop: true }); // class !== plain object
// Type checks
expect(value).toBeDefined();
expect(value).toBeUndefined();
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeNaN();
expect(value).toBeTypeOf("number"); // typeof check
expect(obj).toBeInstanceOf(MyClass);
```
## Number Matchers
```ts
expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(3);
expect(value).toBeLessThan(3);
expect(value).toBeLessThanOrEqual(3);
// Floating point (avoids rounding errors)
expect(0.1 + 0.2).toBeCloseTo(0.3, 5); // numDigits default: 5
```
## String Matchers
```ts
expect("hello world").toContain("world");
expect("hello").toMatch(/^hel/);
expect("hello").toMatch("ell");
```
## Array/Object Matchers
```ts
// Contains item
expect(["a", "b", "c"]).toContain("b");
expect("hello").toContain("ell");
// Contains item with deep equality
expect([{ a: 1 }]).toContainEqual({ a: 1 });
// Has length
expect([1, 2, 3]).toHaveLength(3);
expect("hello").toHaveLength(5);
// Has property
expect(obj).toHaveProperty("a.b.c");
expect(obj).toHaveProperty("a.b", "value");
expect(obj).toHaveProperty(["a", "b"], "value"); // key array
// Partial object match
expect({ a: 1, b: 2 }).toMatchObject({ a: 1 });
expect([{ a: 1 }, { b: 2 }]).toMatchObject([{ a: 1 }, { b: 2 }]);
```
## Error Matchers
```ts
// Throws error
expect(() => throwFn()).toThrow();
expect(() => throwFn()).toThrow("message");
expect(() => throwFn()).toThrow(/regex/);
expect(() => throwFn()).toThrow(ErrorClass);
expect(() => throwFn()).toThrowError(expected); // alias for toThrow
```
## Snapshot Matchers
```ts
// External snapshot file
expect(data).toMatchSnapshot();
expect(data).toMatchSnapshot("custom hint");
expect(data).toMatchSnapshot({ optional: true }); // optional shape
// Inline snapshot (auto-updates)
expect(data).toMatchInlineSnapshot();
expect(data).toMatchInlineSnapshot(`
{
"foo": "bar"
}
`);
// File snapshot (saves to separate file)
expect(data).toMatchFileSnapshot("./output.txt");
```
## Mock Matchers
```ts
import { vi, expect } from "vitest";
const mock = vi.fn();
// Called
expect(mock).toHaveBeenCalled();
expect(mock).toHaveBeenCalledTimes(2);
// Arguments
expect(mock).toHaveBeenCalledWith("arg1", "arg2");
expect(mock).toHaveBeenLastCalledWith("arg");
expect(mock).toHaveBeenNthCalledWith(2, "arg"); // 2nd call
// Return values
expect(mock).toHaveReturned();
expect(mock).toHaveReturnedTimes(2);
expect(mock).toHaveReturnedWith("value");
expect(mock).toHaveLastReturnedWith("value");
expect(mock).toHaveNthReturnedWith(2, "value");
```
## Async Matchers
```ts
// Promise resolution
await expect(Promise.resolve("ok")).resolves.toBe("ok");
await expect(Promise.resolve(obj)).resolves.toEqual({ a: 1 });
// Promise rejection
await expect(Promise.reject("error")).rejects.toBe("error");
await expect(Promise.reject(new Error())).rejects.toThrow();
// Polling (retries until passes or times out)
await expect.poll(() => fetchStatus()).toBe("ready");
await expect
.poll(() => result, {
interval: 50, // check interval (default: 50ms)
timeout: 5000, // max wait (default: 1000ms)
message: "custom error message",
})
.toBe("expected");
```
## Soft Assertions
```ts
// Continue test even if assertion fails
expect.soft(value).toBe(1);
expect.soft(other).toBe(2);
// Test fails at end if any soft assertion failed
```
## Assertion Counting
```ts
test("has correct assertions", () => {
expect.assertions(2); // exactly 2 assertions must run
expect.hasAssertions(); // at least 1 assertion must run
// Unreachable (utility)
if (condition) {
expect.unreachable("should not reach here");
}
});
```
## Custom Matchers
```ts
import { expect } from "vitest";
expect.extend({
toBeEven(received) {
const pass = received % 2 === 0;
return {
pass,
message: () => (pass ? `expected ${received} not to be even` : `expected ${received} to be even`),
};
},
});
// TypeScript
interface CustomMatchers<R = unknown> {
toBeEven(): R;
}
declare module "vitest" {
interface Assertion<T> extends CustomMatchers<T> {}
interface AsymmetricMatchersContaining extends CustomMatchers {}
}
// Usage
expect(4).toBeEven();
expect(3).not.toBeEven();
```
## Asymmetric Matchers
```ts
// Match anything (not null/undefined)
expect(fn).toHaveBeenCalledWith(expect.anything());
// Match by type
expect(fn).toHaveBeenCalledWith(expect.any(Number));
expect(fn).toHaveBeenCalledWith(expect.any(String));
// Partial array
expect(arr).toEqual(expect.arrayContaining([1, 2]));
// Partial object
expect(obj).toEqual(expect.objectContaining({ key: "value" }));
// String patterns
expect(obj).toEqual({
name: expect.stringContaining("John"),
email: expect.stringMatching(/@/),
});
// Negation
expect(arr).toEqual(expect.not.arrayContaining([4]));
expect(str).not.toEqual(expect.stringContaining("foo"));
// Closeness for numbers
expect(arr).toEqual([expect.closeTo(10.1, 1)]);
```
## Modifier: not
```ts
// Negate any matcher
expect(value).not.toBe(other);
expect(arr).not.toContain(item);
expect(fn).not.toHaveBeenCalled();
```
## Important Notes
- **Floating point**: Use `toBeCloseTo` for float comparisons
- **Reference equality**: `toBe` uses `Object.is`, not `===`
- **Undefined vs missing**: `toStrictEqual` distinguishes them
- **Concurrent tests**: Use `expect` from context for reliable snapshots
- **Mock assertions**: Only work with spy functions from `vi.fn()` or `vi.spyOn()`
```
### references/mocking.md
```markdown
# Vitest Mocking Reference
Comprehensive guide to mocking functions, modules, timers, and globals.
## Creating Mocks
### vi.fn() - Mock Function
```ts
import { vi, expect } from "vitest";
// Empty mock (returns undefined)
const mock = vi.fn();
// With implementation
const mockFn = vi.fn((x: number) => x + 1);
// Assertions
mockFn(5);
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledWith(5);
expect(mockFn).toHaveReturnedWith(6);
```
### vi.spyOn() - Spy on Method
```ts
const cart = {
getApples: () => 42,
};
const spy = vi.spyOn(cart, "getApples");
cart.getApples();
expect(spy).toHaveBeenCalled();
expect(spy).toHaveReturnedWith(42);
// With replacement
spy.mockImplementation(() => 100);
expect(cart.getApples()).toBe(100);
// Restore original
spy.mockRestore();
```
### vi.mockObject() (v3.2.0+)
```ts
const original = {
simple: () => "value",
nested: { method: () => "real" },
prop: "foo",
};
const mocked = vi.mockObject(original);
mocked.simple.mockReturnValue("mocked");
// With spy mode (keep implementations)
const spied = vi.mockObject(original, { spy: true });
```
## Mock Properties
```ts
const mock = vi.fn();
mock("arg1", "arg2");
mock("arg3");
// All calls
mock.mock.calls; // [['arg1', 'arg2'], ['arg3']]
mock.mock.lastCall; // ['arg3']
mock.mock.results; // [{ type: 'return', value: undefined }, ...]
mock.mock.settledResults; // For async - { type: 'fulfilled'/'rejected', value }
mock.mock.instances; // Instances when called with `new`
mock.mock.contexts; // `this` values
mock.mock.invocationCallOrder; // [1, 2, ...]
```
## Mock Return Values
```ts
const mock = vi.fn();
// Always return value
mock.mockReturnValue(42);
// Return value once (chainable)
mock.mockReturnValueOnce("first").mockReturnValueOnce("second").mockReturnValue("default");
// For promises
mock.mockResolvedValue({ data: "ok" });
mock.mockResolvedValueOnce({ data: "first" });
mock.mockRejectedValue(new Error("fail"));
mock.mockRejectedValueOnce(new Error("once"));
// Return this
mock.mockReturnThis();
```
## Mock Implementations
```ts
const mock = vi.fn();
// Permanent implementation
mock.mockImplementation((x) => x * 2);
// One-time implementations
mock.mockImplementationOnce(() => "first").mockImplementationOnce(() => "second");
// Temporary implementation
mock.withImplementation(
() => "temp",
() => {
mock(); // 'temp'
}
);
mock(); // back to original
```
## Reset/Clear/Restore
```ts
// Clear call history only
mock.mockClear();
// Clear history + reset implementation
mock.mockReset();
// Clear + reset + restore original (for spies)
mock.mockRestore();
// Global versions
vi.clearAllMocks(); // clearMocks config
vi.resetAllMocks(); // mockReset config
vi.restoreAllMocks(); // restoreMocks config
```
## Module Mocking
### vi.mock() - Hoisted
```ts
import { myFunc } from "./module";
// Automock (returns undefined for all exports)
vi.mock("./module");
// Factory (hoisted to top)
vi.mock("./module", () => ({
myFunc: vi.fn(() => "mocked"),
default: { key: "value" }, // for default export
}));
// With spy mode (keeps implementation)
vi.mock("./module", { spy: true });
// Access original inside factory
vi.mock("./module", async (importOriginal) => {
const mod = await importOriginal();
return { ...mod, myFunc: vi.fn() };
});
```
### vi.doMock() - Not Hoisted
```ts
// For dynamic imports (not hoisted)
vi.doMock("./module", () => ({ myFunc: () => "mocked" }));
const { myFunc } = await import("./module");
```
### vi.unmock() / vi.doUnmock()
```ts
vi.unmock("./module"); // Hoisted
vi.doUnmock("./module"); // Not hoisted
```
### vi.hoisted() - Define Variables Before Imports
```ts
// Variables defined in vi.hoisted are available in vi.mock
const mocks = vi.hoisted(() => ({
myFunc: vi.fn(),
}));
vi.mock("./module", () => ({
myFunc: mocks.myFunc,
}));
mocks.myFunc.mockReturnValue(100);
```
### Helper Functions
```ts
// Import original (bypass mock)
const original = await vi.importActual("./module");
// Import with auto-mock
const mocked = await vi.importMock("./module");
// Type helper
vi.mocked(myFunc).mockReturnValue("typed");
vi.mocked(myFunc, { deep: true }); // Deep mock types
// Check if mocked
vi.isMockFunction(myFunc); // boolean
```
### **mocks** Folder
```
project/
├── __mocks__/
│ └── axios.js # Mock for node_modules
├── src/
│ ├── __mocks__/
│ │ └── utils.js # Mock for ./utils
│ └── utils.js
```
```ts
// Auto-uses __mocks__/axios.js
vi.mock("axios");
```
## Fake Timers
### Enable/Disable
```ts
// Enable fake timers
vi.useFakeTimers();
// Restore real timers
vi.useRealTimers();
// Check if fake timers active
vi.isFakeTimers(); // boolean
```
### Advance Time
```ts
vi.useFakeTimers();
setTimeout(() => console.log("done"), 1000);
// Advance by milliseconds
vi.advanceTimersByTime(1000);
// Advance to next timer
vi.advanceTimersToNextTimer();
// Run all timers
vi.runAllTimers();
// Run only pending (not new ones)
vi.runOnlyPendingTimers();
// For requestAnimationFrame
vi.advanceTimersToNextFrame();
// Async versions (for async callbacks)
await vi.advanceTimersByTimeAsync(1000);
await vi.runAllTimersAsync();
```
### System Time
```ts
vi.useFakeTimers();
// Set system time
vi.setSystemTime(new Date(2024, 0, 1));
expect(Date.now()).toBe(new Date(2024, 0, 1).valueOf());
// Get mocked time
vi.getMockedSystemTime(); // Date | null
// Get real time even with fake timers
vi.getRealSystemTime(); // number
```
### Timer Utilities
```ts
// Count pending timers
vi.getTimerCount();
// Clear all scheduled timers
vi.clearAllTimers();
// Run all microtasks (process.nextTick)
vi.runAllTicks();
```
## Environment & Globals
### vi.stubEnv()
```ts
vi.stubEnv("NODE_ENV", "production");
process.env.NODE_ENV === "production";
import.meta.env.NODE_ENV === "production";
// Restore all
vi.unstubAllEnvs();
```
### vi.stubGlobal()
```ts
vi.stubGlobal("innerWidth", 1024);
vi.stubGlobal("IntersectionObserver", MockObserver);
// Restore all
vi.unstubAllGlobals();
```
## Utilities
### vi.waitFor() - Poll Until Success
```ts
await vi.waitFor(
() => {
if (!server.isReady) throw new Error("Not ready");
},
{ timeout: 5000, interval: 100 }
);
```
### vi.waitUntil() - Poll Until Truthy
```ts
const element = await vi.waitUntil(() => document.querySelector(".loaded"), { timeout: 5000 });
```
### vi.dynamicImportSettled()
```ts
// Wait for all dynamic imports to resolve
function renderComponent() {
import("./component").then(({ render }) => render());
}
renderComponent();
await vi.dynamicImportSettled();
```
### vi.setConfig() / vi.resetConfig()
```ts
vi.setConfig({
testTimeout: 10000,
clearMocks: true,
fakeTimers: { now: new Date(2024, 0, 1) },
});
vi.resetConfig(); // Restore original
```
## Common Patterns
### Auto-restore Mocks
```ts
// vitest.config.ts
export default {
test: {
clearMocks: true, // mockClear before each
mockReset: true, // mockReset before each
restoreMocks: true, // mockRestore before each
unstubEnvs: true, // unstubAllEnvs after each
unstubGlobals: true, // unstubAllGlobals after each
},
};
```
### Using Statement (Auto-cleanup)
```ts
// With explicit resource management
it('test', () => {
using spy = vi.spyOn(console, 'log')
// spy.mockRestore() called automatically at block end
})
```
### Module Reset Between Tests
```ts
beforeEach(() => {
vi.resetModules();
});
test("test", async () => {
const { state } = await import("./module");
// Fresh module instance
});
```
```
### references/config.md
```markdown
# Vitest Configuration Reference
Complete configuration options for vitest.config.ts or vite.config.ts.
## Config File Setup
### Standalone vitest.config.ts (Recommended)
```ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
// test options here
},
});
```
### Using vite.config.ts
```ts
/// <reference types="vitest/config" />
import { defineConfig } from "vite";
export default defineConfig({
test: {
// test options here
},
});
```
### Extending Vite Config
```ts
import { defineConfig, mergeConfig } from "vitest/config";
import viteConfig from "./vite.config";
export default mergeConfig(
viteConfig,
defineConfig({
test: {
exclude: ["packages/template/*"],
},
})
);
```
### Using Defaults
```ts
import { configDefaults, defineConfig } from "vitest/config";
export default defineConfig({
test: {
exclude: [...configDefaults.exclude, "packages/template/*"],
},
});
```
## Essential Options
### Test File Patterns
```ts
{
// Files to include (glob patterns)
include: ['**/*.{test,spec}.?(c|m)[jt]s?(x)'],
// Files to exclude
exclude: ['**/node_modules/**', '**/.git/**'],
// In-source testing
includeSource: ['src/**/*.{js,ts}'],
}
```
### Globals
```ts
{
// Enable global test APIs (test, expect, describe, etc.)
globals: true,
}
// Add to tsconfig.json for TypeScript:
// { "compilerOptions": { "types": ["vitest/globals"] } }
```
### Environment
```ts
{
// 'node' | 'jsdom' | 'happy-dom' | 'edge-runtime' | string
environment: 'jsdom',
// Environment options
environmentOptions: {
jsdom: {
url: 'http://localhost:3000',
},
},
}
// Per-file environment (docblock at top of file):
// /** @vitest-environment jsdom */
```
### Pool (Test Runner)
```ts
{
// 'threads' | 'forks' | 'vmThreads' | 'vmForks'
pool: 'forks', // default
// Pool-specific options
poolOptions: {
threads: {
singleThread: true,
},
forks: {
singleFork: true,
},
},
}
```
**Pool Types:**
- `threads` - Worker threads (fast, but can't use `process.chdir()`)
- `forks` - Child process (default, supports process APIs)
- `vmThreads` - VM context in threads (fastest, but unstable ESM)
- `vmForks` - VM context in forks
## Timeouts
```ts
{
testTimeout: 5000, // Per-test timeout (default: 5000ms)
hookTimeout: 10000, // Setup/teardown timeout (default: 10000ms)
teardownTimeout: 10000, // Global teardown timeout
}
```
## Setup Files
```ts
{
// Run before each test file
setupFiles: ['./test/setup.ts'],
// Run once before all tests
globalSetup: ['./test/global-setup.ts'],
}
```
## Mock Configuration
```ts
{
clearMocks: true, // Clear mock calls before each test
mockReset: true, // Reset mock implementations before each test
restoreMocks: true, // Restore original implementations before each test
unstubEnvs: true, // Restore env vars after each test
unstubGlobals: true, // Restore globals after each test
}
```
## Coverage
```ts
{
coverage: {
enabled: true,
provider: 'v8', // 'v8' | 'istanbul'
reporter: ['text', 'json', 'html'],
reportsDirectory: './coverage',
// Files to include/exclude
include: ['src/**/*.ts'],
exclude: ['**/*.test.ts', '**/*.d.ts'],
// Thresholds (fail if below)
thresholds: {
lines: 80,
functions: 80,
branches: 80,
statements: 80,
perFile: true, // Check per file
autoUpdate: true, // Auto-update thresholds
100: false, // Require 100% coverage
},
// Skip files with 100% coverage in report
skipFull: false,
// Generate report even if tests fail
reportOnFailure: true,
},
}
```
## Reporters
```ts
{
// Built-in reporters
reporters: ['default'],
// Options: 'basic', 'default', 'verbose', 'dot', 'json', 'html',
// 'junit', 'github-actions', 'blob', 'tap', 'tap-flat'
// With options
reporters: [
['json', { outputFile: './test-results.json' }],
['junit', { outputFile: './junit.xml' }],
],
// Output file shorthand
outputFile: {
json: './test-results.json',
junit: './junit.xml',
},
}
```
## Watch Mode
```ts
{
watch: true, // Enable watch mode (default in dev)
// Patterns that force full rerun
forceRerunTriggers: ['**/package.json', '**/*.config.*'],
// Watch-specific patterns
watchTriggerPatterns: ['src/**', 'test/**'],
}
```
## Test Filtering
```ts
{
// Filter by test name pattern
testNamePattern: /should.*work/,
// Allow test.only in CI
allowOnly: false, // default: !process.env.CI
// Pass with no test files
passWithNoTests: false,
}
```
## Parallelism
```ts
{
// Run test files in parallel
fileParallelism: true,
// Max concurrent test files
maxWorkers: 4,
// Max concurrent tests within a file
maxConcurrency: 5,
// Isolate test files
isolate: true,
}
```
## Sequences
```ts
{
sequence: {
// Hook execution order: 'stack' | 'list' | 'parallel'
hooks: 'parallel',
// Shuffle tests
shuffle: false,
seed: 123, // Shuffle seed
},
}
```
## Snapshots
```ts
{
// Snapshot format options
snapshotFormat: {
printBasicPrototype: false,
},
// Custom serializers
snapshotSerializers: ['./custom-serializer.ts'],
// Custom snapshot path resolver
resolveSnapshotPath: (path, ext) => path.replace('src', '__snapshots__') + ext,
}
```
## Retry & Bail
```ts
{
retry: 2, // Retry failed tests N times
bail: 1, // Stop after N failures (0 = no bail)
}
```
## Typecheck
```ts
{
typecheck: {
enabled: true,
checker: 'tsc', // 'tsc' | 'vue-tsc'
include: ['**/*.{test,spec}-d.?(c|m)[jt]s?(x)'],
},
}
```
## Fake Timers
```ts
{
fakeTimers: {
// Which APIs to mock
toFake: ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'Date'],
// Loop limit for runAllTimers
loopLimit: 10000,
},
}
```
## Benchmarks
```ts
{
benchmark: {
include: ['**/*.{bench,benchmark}.?(c|m)[jt]s?(x)'],
exclude: ['**/node_modules/**'],
outputFile: './bench/results.json',
reporters: ['default'],
},
}
```
## UI
```ts
{
ui: true, // Enable Vitest UI
open: true, // Auto-open in browser
api: {
port: 51204,
strictPort: true,
},
}
```
## Multi-Project Configuration
```ts
{
projects: [
{
name: 'unit',
include: ['test/unit/**/*.test.ts'],
environment: 'node',
},
{
name: 'browser',
include: ['test/browser/**/*.test.ts'],
browser: {
enabled: true,
provider: 'playwright',
instances: [{ browser: 'chromium' }],
},
},
],
}
```
## Browser Mode
```ts
{
browser: {
enabled: true,
provider: 'playwright', // 'playwright' | 'webdriverio' | 'preview'
instances: [
{ browser: 'chromium' },
{ browser: 'firefox' },
{ browser: 'webkit' },
],
headless: true,
viewport: { width: 1280, height: 720 },
// Screenshot on failure
screenshotFailures: true,
screenshotDirectory: './screenshots',
},
}
```
## CLI Options
```bash
# Run tests
vitest
vitest run # Run once (no watch)
vitest watch # Watch mode
# Filtering
vitest src/utils # Run tests in path
vitest --testNamePattern="should work"
vitest --exclude "**/*.integration.test.ts"
# Coverage
vitest --coverage
vitest --coverage.enabled --coverage.provider=istanbul
# Environment
vitest --environment jsdom
vitest --pool threads
# Other
vitest --ui # Open UI
vitest --reporter=json # Change reporter
vitest bench # Run benchmarks
vitest typecheck # Run type checking
vitest --bail 1 # Stop on first failure
vitest --retry 2 # Retry failed tests
```
## OpenTelemetry (Experimental)
Enable distributed tracing for test execution. Requires `@opentelemetry/sdk-node`.
```bash
npm install @opentelemetry/sdk-node
```
```ts
// otel-setup.ts
import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
export async function setup() {
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: "http://localhost:4318/v1/traces",
}),
});
sdk.start();
return async () => await sdk.shutdown();
}
```
```ts
// vitest.config.ts
export default defineConfig({
test: {
experimental: {
openTelemetry: {
enabled: true,
sdkPath: "./otel-setup.ts",
},
},
},
});
```
### Browser Mode OpenTelemetry
```ts
{
browser: {
enabled: true,
// ...
},
experimental: {
openTelemetry: {
enabled: true,
sdkPath: './otel-setup-node.ts', // Node.js SDK
browserSdkPath: './otel-setup-browser.ts', // Browser SDK
},
},
}
```
### CI/CD Context Propagation
Pass trace context via environment variables:
```bash
TRACEPARENT="00-<trace-id>-<span-id>-01" vitest run
TRACESTATE="key=value" vitest run
```
This links test spans to parent CI pipeline traces.
## Config Hierarchy
1. CLI flags (highest priority)
2. `vitest.config.ts`
3. `vite.config.ts`
4. Defaults
**Note:** Configuration options marked with 🚫 in docs can only be set at root level, not in project configs.
```
### references/cli.md
```markdown
# Vitest CLI Reference
Command line interface for running tests.
## Basic Commands
```bash
# Run tests in watch mode (default in dev)
vitest
# Run tests once (no watch)
vitest run
# Watch mode (explicit)
vitest watch
# Run benchmarks
vitest bench
# Run type checking
vitest typecheck
# List matching tests without running
vitest list
vitest list --filesOnly
# Initialize browser testing
vitest init browser
```
## Test Filtering
```bash
# Filter by file path (contains "foobar")
vitest foobar
# Filter by filename and line number
vitest src/utils.test.ts:10
vitest ./src/utils.test.ts:10
vitest /absolute/path/utils.test.ts:10
# Filter by test name pattern
vitest -t "should work"
vitest --testNamePattern="user.*login"
# Exclude patterns
vitest --exclude "**/*.e2e.test.ts"
# Run related tests (for lint-staged)
vitest related src/index.ts src/utils.ts --run
# Run only changed files
vitest --changed
vitest --changed HEAD~1
vitest --changed origin/main
```
## Coverage
```bash
# Enable coverage
vitest --coverage
vitest run --coverage
# With options
vitest --coverage.enabled --coverage.provider=istanbul
vitest --coverage.reporter=html --coverage.reporter=json
# Thresholds
vitest --coverage.thresholds.100
vitest --coverage.thresholds.lines=80
vitest --coverage.thresholds.functions=80
vitest --coverage.thresholds.branches=80
vitest --coverage.thresholds.statements=80
# Output directory
vitest --coverage.reportsDirectory=./reports
```
## Environment
```bash
# Set environment
vitest --environment=jsdom
vitest --environment=happy-dom
vitest --environment=node
# Set pool
vitest --pool=threads
vitest --pool=forks
vitest --pool=vmThreads
# Enable globals
vitest --globals
```
## Watch Mode Options
```bash
# Disable watch (same as "run")
vitest --run
# Start without running tests (run on change)
vitest --standalone
# Clear screen on rerun
vitest --clearScreen
```
## Reporters
```bash
# Use specific reporter
vitest --reporter=verbose
vitest --reporter=dot
vitest --reporter=json
vitest --reporter=junit
vitest --reporter=html
vitest --reporter=github-actions
# Multiple reporters
vitest --reporter=default --reporter=json
# Output to file
vitest --outputFile=./results.json
vitest --outputFile.json=./json.json --outputFile.junit=./junit.xml
```
## UI
```bash
# Open Vitest UI
vitest --ui
# Auto-open in browser
vitest --ui --open
# Specify port
vitest --api.port=51204
```
## Debugging
```bash
# Enable Node.js inspector
vitest --inspect
vitest --inspect=127.0.0.1:9229
# Break before test starts
vitest --inspectBrk
```
## Timeouts
```bash
# Test timeout (default: 5000ms)
vitest --testTimeout=10000
# Hook timeout (default: 10000ms)
vitest --hookTimeout=30000
# Teardown timeout
vitest --teardownTimeout=10000
```
## Parallelism
```bash
# Max worker threads
vitest --maxWorkers=4
# Disable file parallelism
vitest --no-file-parallelism
# Disable isolation
vitest --no-isolate
# Max concurrent tests
vitest --maxConcurrency=10
```
## Retry & Bail
```bash
# Retry failed tests
vitest --retry=3
# Stop after N failures
vitest --bail=1
```
## Sharding (CI)
```bash
# Split tests into 3 parts
vitest run --shard=1/3
vitest run --shard=2/3
vitest run --shard=3/3
# Merge reports from shards
vitest --merge-reports --reporter=junit
```
## Browser Mode
```bash
# Enable browser testing
vitest --browser.enabled
vitest --browser.name=chromium
vitest --browser.name=firefox
vitest --browser.name=webkit
# Headless mode
vitest --browser.headless
# Browser UI
vitest --browser.ui
```
## Type Checking
```bash
# Enable type checking
vitest --typecheck.enabled
# Run only type tests
vitest typecheck
vitest --typecheck.only
# Specify checker
vitest --typecheck.checker=tsc
vitest --typecheck.checker=vue-tsc
```
## Project Selection
```bash
# Run specific project
vitest --project=unit
vitest --project=e2e
# Multiple projects
vitest --project=unit --project=integration
# Wildcard
vitest --project="packages*"
# Exclude
vitest --project="!e2e"
```
## Output Control
```bash
# Silent mode
vitest --silent
# Only show failed test logs
vitest --silent=passed-only
# Hide skipped tests
vitest --hideSkippedTests
# Disable colors
vitest --no-color
# Log heap usage
vitest --logHeapUsage
```
## Snapshots
```bash
# Update snapshots
vitest -u
vitest --update
# Expand snapshot diff
vitest --expandSnapshotDiff
```
## Sequences
```bash
# Shuffle tests
vitest --sequence.shuffle.tests
vitest --sequence.shuffle.files
# Set shuffle seed
vitest --sequence.seed=12345
# Run concurrently
vitest --sequence.concurrent
```
## Configuration
```bash
# Use specific config
vitest --config=./vitest.e2e.config.ts
vitest -c ./custom-config.ts
# Set root directory
vitest --root=./packages/core
vitest -r ./packages/core
# Set test directory
vitest --dir=./tests
# Clear cache
vitest --clearCache
```
## Common Patterns
### CI Pipeline
```bash
# Basic CI run
vitest run --coverage --reporter=junit --outputFile=./junit.xml
# With coverage thresholds
vitest run --coverage --coverage.thresholds.100
```
### lint-staged Integration
```js
// .lintstagedrc.js
export default {
"*.{js,ts}": "vitest related --run",
};
```
### Watch Specific Files
```bash
vitest --watch src/utils
```
### Debug Single Test
```bash
vitest --inspect-brk src/specific.test.ts
```
## Exit Codes
- `0` - All tests passed
- `1` - Tests failed or errors occurred
- Non-zero with `--bail` if threshold exceeded
```
### references/browser.md
```markdown
# Vitest Browser Mode Reference
Run tests in real browsers for accurate DOM testing.
## Why Browser Mode?
- **Real browser environment** - No simulation (jsdom/happy-dom) discrepancies
- **Native browser APIs** - Access to window, document, and browser-specific features
- **Component testing** - Test React, Vue, Svelte, etc. in actual browsers
- **Visual regression** - Screenshot and compare UI changes
## Installation
### Quick Setup
```bash
npx vitest init browser
```
### Manual Setup
```bash
# For Playwright (recommended)
npm install -D vitest @vitest/browser-playwright
# For WebdriverIO
npm install -D vitest @vitest/browser-webdriverio
# For preview only (not for CI)
npm install -D vitest @vitest/browser-preview
```
## Configuration
### Basic Setup
```ts
// vitest.config.ts
import { defineConfig } from "vitest/config";
import { playwright } from "@vitest/browser-playwright";
export default defineConfig({
test: {
browser: {
enabled: true,
provider: playwright(),
instances: [{ browser: "chromium" }],
},
},
});
```
### With Framework (React Example)
```ts
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import { playwright } from "@vitest/browser-playwright";
export default defineConfig({
plugins: [react()],
test: {
browser: {
enabled: true,
provider: playwright(),
instances: [{ browser: "chromium" }],
},
},
});
```
### Multi-Browser Testing
```ts
{
browser: {
enabled: true,
provider: playwright(),
instances: [
{ browser: 'chromium' },
{ browser: 'firefox' },
{ browser: 'webkit' },
],
},
}
```
### Headless Mode
```ts
{
browser: {
enabled: true,
provider: playwright(),
headless: true, // Run without UI
instances: [{ browser: 'chromium' }],
},
}
```
### Mixed Node + Browser Projects
```ts
export default defineConfig({
test: {
projects: [
{
test: {
name: "unit",
include: ["tests/unit/**/*.test.ts"],
environment: "node",
},
},
{
test: {
name: "browser",
include: ["tests/browser/**/*.test.ts"],
browser: {
enabled: true,
provider: playwright(),
instances: [{ browser: "chromium" }],
},
},
},
],
},
});
```
## Available Browsers
**Playwright:**
- `chromium`
- `firefox`
- `webkit`
**WebdriverIO:**
- `chrome`
- `firefox`
- `edge`
- `safari`
## Writing Browser Tests
### Basic Test
```ts
import { expect, test } from "vitest";
import { page } from "vitest/browser";
test("renders content", async () => {
document.body.innerHTML = "<div>Hello World</div>";
await expect.element(page.getByText("Hello World")).toBeInTheDocument();
});
```
### With Locators
```ts
import { page, userEvent } from "vitest/browser";
test("form interaction", async () => {
// Find elements
const input = page.getByLabelText(/username/i);
const button = page.getByRole("button", { name: /submit/i });
// Interact
await input.fill("john");
await button.click();
// Assert
await expect.element(page.getByText("Welcome, john")).toBeVisible();
});
```
### Locator Methods
```ts
import { page } from "vitest/browser";
// By role (recommended)
page.getByRole("button", { name: "Submit" });
page.getByRole("textbox", { name: /email/i });
page.getByRole("heading", { level: 1 });
// By text
page.getByText("Hello World");
page.getByText(/hello/i); // regex
// By label
page.getByLabelText("Username");
// By placeholder
page.getByPlaceholder("Enter email");
// By test ID
page.getByTestId("submit-button");
// By title
page.getByTitle("Close dialog");
// By alt text
page.getByAltText("Profile picture");
// CSS selector (escape hatch)
page.elementLocator(document.querySelector(".my-class"));
```
### User Events
```ts
import { userEvent, page } from "vitest/browser";
// Typing
await userEvent.type(input, "Hello");
await input.fill("Hello"); // Alternative
// Clicking
await userEvent.click(button);
await button.click(); // Alternative
// Keyboard
await userEvent.keyboard("{Enter}");
// Hover
await userEvent.hover(element);
// Focus
await userEvent.focus(input);
// Select
await userEvent.selectOptions(select, ["option1"]);
// File upload
await userEvent.upload(fileInput, file);
// Drag and drop
await userEvent.dragAndDrop(source, target);
```
### Browser Assertions
```ts
import { expect } from "vitest";
import { page } from "vitest/browser";
// Element exists
await expect.element(page.getByText("Hello")).toBeInTheDocument();
// Visibility
await expect.element(locator).toBeVisible();
await expect.element(locator).not.toBeVisible();
// Enabled/Disabled
await expect.element(button).toBeEnabled();
await expect.element(button).toBeDisabled();
// Value
await expect.element(input).toHaveValue("text");
// Text content
await expect.element(heading).toHaveTextContent("Title");
// Attribute
await expect.element(link).toHaveAttribute("href", "/home");
// Class
await expect.element(element).toHaveClass("active");
// Focus
await expect.element(input).toBeFocused();
```
## Component Testing
### React
```bash
npm install -D vitest-browser-react
```
```tsx
import { render } from "vitest-browser-react";
import { expect, test } from "vitest";
import Button from "./Button";
test("button click", async () => {
const screen = render(<Button>Click me</Button>);
await screen.getByRole("button").click();
await expect.element(screen.getByText("Clicked!")).toBeVisible();
});
```
### Vue
```bash
npm install -D vitest-browser-vue
```
```ts
import { render } from "vitest-browser-vue";
import Component from "./Component.vue";
test("v-model works", async () => {
const screen = render(Component);
await screen.getByLabelText(/username/i).fill("Bob");
await expect.element(screen.getByText("Hi, Bob")).toBeInTheDocument();
});
```
### Svelte
```bash
npm install -D vitest-browser-svelte
```
```ts
import { render } from "vitest-browser-svelte";
import Counter from "./Counter.svelte";
test("counter increments", async () => {
const screen = render(Counter);
await screen.getByRole("button").click();
await expect.element(screen.getByText("1")).toBeVisible();
});
```
## Visual Regression Testing
```ts
import { page } from "vitest/browser";
test("visual snapshot", async () => {
// Full page screenshot
await expect(page.screenshot()).toMatchImageSnapshot();
// Element screenshot
const element = page.getByTestId("card");
await expect(element.screenshot()).toMatchImageSnapshot();
});
```
## Viewport Control
```ts
import { page } from "vitest/browser";
test("responsive design", async () => {
// Set viewport
await page.viewport(375, 667); // iPhone SE
await expect.element(page.getByTestId("mobile-menu")).toBeVisible();
await page.viewport(1920, 1080); // Desktop
await expect.element(page.getByTestId("desktop-nav")).toBeVisible();
});
```
## Running Browser Tests
```bash
# Run with browser mode
vitest --browser.enabled
# Specific browser
vitest --browser.name=chromium
# Headless
vitest --browser.headless
# With UI
vitest --browser.ui
```
## Limitations
### No vi.spyOn on Imports
```ts
// ❌ Doesn't work in browser mode
import * as module from "./module";
vi.spyOn(module, "method");
// ✅ Use vi.mock with spy option
vi.mock("./module", { spy: true });
```
### Blocking Dialogs Mocked
`alert()`, `confirm()`, `prompt()` are automatically mocked because they block execution. Mock them explicitly for predictable behavior.
## Best Practices
1. **Use Playwright provider** for CI - supports parallel execution
2. **Use locators by role** - most resilient selectors
3. **Use `expect.element()`** - waits for element automatically
4. **Avoid `testing-library/user-event`** - use `vitest/browser` instead
5. **Use headless mode** in CI
6. **Separate browser tests** into their own project
```