home / skills / knoopx / pi / vitest
npx playbooks add skill knoopx/pi --skill vitestReview the files below or copy the command above to add this skill to your agents.
---
name: vitest
description: Write and run tests, configure test environments, mock dependencies, and generate coverage reports. Use when setting up vitest.config.ts, writing test suites, mocking modules, or measuring code coverage.
---
# Vitest Cheatsheet
Fast unit testing framework powered by Vite.
## Contents
- [Setup](#setup)
- [Running Tests](#running-tests)
- [BDD Test Structure](#bdd-test-structure)
- [Assertions](#assertions)
- [Mocking](#mocking-bdd-style)
- [Module Mocking](#module-mocking-bdd-style)
- [Hooks](#hooks-bdd-style)
- [Coverage](#coverage)
- [Configuration Options](#configuration-options)
- [Benchmarking](#benchmarking)
## Setup
### Package.json Scripts
```json
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"coverage": "vitest run --coverage"
}
}
```
### Config (vitest.config.ts)
```typescript
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "node", // 'jsdom', 'happy-dom'
globals: true, // use global test functions
setupFiles: ["./setup.ts"],
},
});
```
## Running Tests
```bash
vitest run # Run once
vitest run --coverage # With coverage
vitest --reporter=verbose # Detailed output
vitest bench # Run benchmarks
vitest typecheck # Run type checking
vitest related src/file.ts # Run tests related to file
# Watch mode / Web UI (use tmux for background)
tmux new -d -s vitest 'vitest'
tmux new -d -s vitest-ui 'vitest --ui'
```
## BDD Test Structure
Write tests using Given-When-Then style with nested `describe` blocks:
```typescript
import { describe, it, expect, beforeEach } from "vitest";
describe("Calculator", () => {
describe("given two positive numbers", () => {
const a = 5;
const b = 3;
describe("when adding them", () => {
it("then the result should be their sum", () => {
expect(a + b).toBe(8);
});
});
describe("when subtracting them", () => {
it("then the result should be the difference", () => {
expect(a - b).toBe(2);
});
});
});
describe("given a division by zero", () => {
describe("when dividing", () => {
it("then it should return Infinity", () => {
expect(1 / 0).toBe(Infinity);
});
});
});
});
```
## Assertions
```typescript
expect(value).toBe(expected);
expect(value).toEqual(expected);
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toContain(item);
expect(value).toHaveLength(length);
expect(value).toThrow(error);
```
## Async Tests (BDD Style)
```typescript
import { describe, it, expect, beforeEach } from "vitest";
describe("UserAPI", () => {
describe("given a valid user ID", () => {
const userId = 1;
describe("when fetching the user", () => {
it("then the user data should be returned", async () => {
const user = await fetchUser(userId);
expect(user).toBeDefined();
expect(user.id).toBe(userId);
});
});
});
describe("given an invalid user ID", () => {
const userId = -1;
describe("when fetching the user", () => {
it("then it should throw a NotFoundError", async () => {
await expect(fetchUser(userId)).rejects.toThrow("User not found");
});
});
});
});
```
## Mocking (BDD Style)
```typescript
import { vi, describe, it, expect, beforeEach } from "vitest";
describe("OrderService", () => {
describe("given a mocked payment gateway", () => {
const mockPaymentGateway = {
charge: vi.fn(),
};
beforeEach(() => {
vi.clearAllMocks();
});
describe("when processing a valid order", () => {
beforeEach(() => {
mockPaymentGateway.charge.mockResolvedValue({ success: true });
});
it("then the payment should be charged", async () => {
const orderService = new OrderService(mockPaymentGateway);
await orderService.process({ amount: 100 });
expect(mockPaymentGateway.charge).toHaveBeenCalledWith(100);
});
it("then the order should be marked as paid", async () => {
const orderService = new OrderService(mockPaymentGateway);
const result = await orderService.process({ amount: 100 });
expect(result.status).toBe("paid");
});
});
describe("when payment fails", () => {
beforeEach(() => {
mockPaymentGateway.charge.mockRejectedValue(new Error("Payment declined"));
});
it("then the order should be marked as failed", async () => {
const orderService = new OrderService(mockPaymentGateway);
await expect(orderService.process({ amount: 100 })).rejects.toThrow("Payment declined");
});
});
});
});
```
## Module Mocking (BDD Style)
```typescript
import { vi, describe, it, expect, beforeEach } from "vitest";
vi.mock("./email-service", () => ({
sendEmail: vi.fn(),
}));
import { sendEmail } from "./email-service";
import { UserService } from "./user-service";
describe("UserService", () => {
describe("given a mocked email service", () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe("when a user registers successfully", () => {
it("then a welcome email should be sent", async () => {
const userService = new UserService();
await userService.register({ email: "[email protected]", name: "Alice" });
expect(sendEmail).toHaveBeenCalledWith(
"[email protected]",
expect.stringContaining("Welcome")
);
});
});
describe("when registration fails due to invalid email", () => {
it("then no email should be sent", async () => {
const userService = new UserService();
await expect(
userService.register({ email: "invalid", name: "Bob" })
).rejects.toThrow();
expect(sendEmail).not.toHaveBeenCalled();
});
});
});
});
```
## Spying (BDD Style)
```typescript
import { vi, describe, it, expect, beforeEach } from "vitest";
describe("Logger", () => {
describe("given a console spy", () => {
let consoleSpy: ReturnType<typeof vi.spyOn>;
beforeEach(() => {
consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
});
afterEach(() => {
consoleSpy.mockRestore();
});
describe("when logging a message", () => {
it("then the message should be written to console", () => {
const logger = new Logger();
logger.info("Hello, World!");
expect(consoleSpy).toHaveBeenCalledWith("[INFO]", "Hello, World!");
});
});
});
});
```
## Hooks (BDD Style)
```typescript
import { describe, it, expect, beforeAll, beforeEach, afterEach, afterAll } from "vitest";
describe("Database Operations", () => {
describe("given a database connection", () => {
let db: Database;
beforeAll(async () => {
db = await Database.connect(":memory:");
});
afterAll(async () => {
await db.close();
});
describe("given an empty users table", () => {
beforeEach(async () => {
await db.exec("DELETE FROM users");
});
describe("when inserting a user", () => {
it("then the user count should be 1", async () => {
await db.insert("users", { name: "Alice" });
const count = await db.count("users");
expect(count).toBe(1);
});
});
describe("when querying users", () => {
it("then the result should be empty", async () => {
const users = await db.query("SELECT * FROM users");
expect(users).toHaveLength(0);
});
});
});
});
});
```
## Error Testing (BDD Style)
```typescript
import { describe, it, expect } from "vitest";
describe("Validator", () => {
describe("given an empty string", () => {
const input = "";
describe("when validating as required field", () => {
it("then it should throw a ValidationError", () => {
expect(() => validateRequired(input)).toThrow("Field is required");
});
it("then the error should be a ValidationError instance", () => {
expect(() => validateRequired(input)).toThrow(ValidationError);
});
});
});
describe("given a valid email", () => {
const email = "[email protected]";
describe("when validating email format", () => {
it("then it should not throw", () => {
expect(() => validateEmail(email)).not.toThrow();
});
});
});
describe("given an invalid email", () => {
const email = "not-an-email";
describe("when validating email format", () => {
it("then it should throw with descriptive message", () => {
expect(() => validateEmail(email)).toThrow(/invalid email format/i);
});
});
});
});
```
## Coverage
```typescript
export default defineConfig({
test: {
coverage: {
reporter: ["text", "json", "html"],
exclude: ["node_modules", "test"],
},
},
});
```
## Environments
```typescript
// Node.js (default)
test: { environment: "node" }
// Browser
test: { environment: "jsdom" }
// Custom
test: {
environment: "jsdom",
setupFiles: ["./setup.js"],
}
```
## Configuration Options
```typescript
export default defineConfig({
test: {
globals: true,
environment: "jsdom",
setupFiles: ["./test/setup.ts"],
testTimeout: 10000,
include: ["**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
exclude: ["**/node_modules/**", "**/dist/**"],
coverage: {
reporter: ["text", "json"],
exclude: ["node_modules/", "src/test/"],
},
},
});
```
## Projects
```typescript
export default defineConfig({
test: {
projects: [
{
name: "unit",
test: { include: ["**/*.test.ts"] },
},
{
name: "integration",
test: { include: ["**/*.spec.ts"] },
},
],
},
});
```
## Browser Testing
```typescript
export default defineConfig({
test: {
browser: {
enabled: true,
name: "chromium",
provider: "playwright",
},
},
});
```
## Benchmarking
```typescript
import { bench, describe } from "vitest";
describe("Sorting Performance", () => {
describe("given an array of 1000 numbers", () => {
const numbers = Array.from({ length: 1000 }, () => Math.random());
bench("when using native sort", () => {
[...numbers].sort((a, b) => a - b);
});
bench("when using custom quicksort", () => {
quicksort([...numbers]);
});
});
});
```
## Type Checking
```bash
vitest typecheck # Check types alongside tests
vitest typecheck --run # Single run
```
## Tips
- Use `globals: true` to avoid importing test functions
- `vitest run` in CI for single run
- `--coverage` generates coverage reports
- Mock external dependencies with `vi.mock()`
- Use `--reporter=verbose` for detailed test output
- Browser mode for real browser testing: `--browser`
- Use tmux for watch mode: `tmux new -d -s vitest 'vitest'`
- Use `vitest bench` for performance testing
- Structure tests with Given-When-Then for clarity