| name | vitest-v4 |
| description | This skill should be used when the user asks to "write tests", "add tests", "test coverage", "run tests", "debug failing tests", "mock functions", or mentions Vitest, unit tests, component tests, test-driven development, or testing utilities. Provides comprehensive Vitest v4 guidance for TypeScript React/Next.js projects. |
Your Role
You are an expert in writing tests with Vitest v4 for TypeScript React/Next.js projects. You help users write
high-quality tests, debug failures, and maintain test suites efficiently.
Typical setup:
- Vitest v4 with jsdom environment
- Globals enabled (
describe, test, expect, vi)
- Path aliases configured per project
Skill delegation:
If the task is related to Zustand store testing, activate the zustand skill
Quick Start
Running Tests
nlx vitest run
nlx vitest run tokens
nlx vitest run src/utils/format.test.ts
nlx vitest run -t "adds token"
nlx vitest
Writing Your First Test
File naming: *.test.ts or *.test.tsx
Location: Colocate with source files
import { describe, test, expect } from "vitest";
import { myFunction } from "./my-function";
describe("myFunction", () => {
test("returns expected value", () => {
expect(myFunction(5)).toBe(10);
});
});
Project-Specific Patterns
Test Organization
Use visual separators and descriptive blocks:
describe("TokenStore", () => {
const validToken = { address: "0x123", symbol: "TEST" };
afterEach(() => {
useTokensStore.getState().clearAll();
});
describe("addToken", () => {
test("adds valid token and returns true", () => {
const success = useTokensStore.getState().addToken(validToken);
expect(success).toBe(true);
});
});
});
Cleanup Pattern
Always reset state in afterEach():
import { afterEach } from "vitest";
afterEach(() => {
vi.clearAllMocks();
process.env.NODE_ENV = originalEnv;
});
Factory Mock Pattern
Prefer factory functions for complex mocks:
import { vi } from "vitest";
export function createLocalStorageMock() {
const store = new Map<string, string>();
return {
getItem: vi.fn((key: string) => store.get(key) ?? null),
setItem: vi.fn((key: string, value: string) => {
store.set(key, value);
}),
removeItem: vi.fn((key: string) => {
store.delete(key);
}),
clear: vi.fn(() => {
store.clear();
})
};
}
import { createLocalStorageMock } from "./__mocks__/localStorage";
const mockStorage = createLocalStorageMock();
global.localStorage = mockStorage as Storage;
Shared Setup File
Global mocks and configuration live in a setup file (e.g., tests/setup.ts):
import { vi } from "vitest";
vi.mock("@/utils/logger", () => ({
createLogger: vi.fn(() => ({
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn()
}))
}));
Common Testing Scenarios
Testing Utilities
import { describe, test, expect, afterEach } from "vitest";
import { getEnvironment } from "./environment";
describe("getEnvironment", () => {
const originalEnv = process.env.NODE_ENV;
afterEach(() => {
process.env.NODE_ENV = originalEnv;
});
test("returns production when NODE_ENV is production", () => {
process.env.NODE_ENV = "production";
expect(getEnvironment()).toBe("production");
});
test("returns development by default", () => {
process.env.NODE_ENV = undefined;
expect(getEnvironment()).toBe("development");
});
});
Async Testing
test("async function resolves correctly", async () => {
const result = await fetchData();
expect(result).toEqual({ data: "value" });
});
test("async function rejects with error", async () => {
await expect(failingFunction()).rejects.toThrow("Error message");
});
Mocking Functions
import { vi } from "vitest";
const mockCallback = vi.fn((x: number) => x * 2);
mockCallback(5);
expect(mockCallback).toHaveBeenCalledWith(5);
expect(mockCallback).toHaveReturnedWith(10);
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
console.log("test");
expect(spy).toHaveBeenCalledWith("test");
spy.mockRestore();
Mocking Modules
vi.mock("./api-client", () => ({
fetchUser: vi.fn(() => Promise.resolve({ id: 1, name: "Test" }))
}));
import { fetchUser } from "./api-client";
test("uses mocked API", async () => {
const user = await fetchUser();
expect(user.name).toBe("Test");
});
Timer Mocking
import { vi } from "vitest";
test("debounced function", () => {
vi.useFakeTimers();
const callback = vi.fn();
const debounced = debounce(callback, 1000);
debounced();
debounced();
debounced();
vi.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalledTimes(1);
vi.useRealTimers();
});
Debugging Failed Tests
Reading Test Output
Focus on these signals:
- File and line number - Where the failure occurred
- Expected vs. received - What went wrong
- Stack trace - Ignore framework internals, focus on your code
Common Failures
State bleeding between tests:
test("first test", () => {
store.addItem("test");
});
test("second test", () => {
expect(store.items).toHaveLength(0);
});
afterEach(() => {
store.clear();
});
Mock not working:
vi.mock("./utils/logger");
import { logger } from "@/utils/logger";
vi.mock("@/utils/logger");
Async timeout:
test("slow operation", async () => {
await verySlowOperation();
});
test("slow operation", async () => {
await verySlowOperation();
}, 10000);
Debugging Tools
nlx vitest --reporter=verbose
nlx vitest --ui
nlx vitest --coverage
nlx vitest --inspect
nlx vitest --run
Best Practices
DO
- Colocate tests with source files (
feature.ts + feature.test.ts)
- Use
describe blocks to group related tests
- Add
afterEach() cleanup for state/mocks
- Use visual separators for clarity (
/* --- */)
- Test behavior, not implementation
- Use explicit type annotations for mocks
- Keep tests focused and independent
- Write tests before fixing bugs (reproduce the bug first)
DON'T
- Test implementation details (internal variables)
- Share state between tests
- Mock everything (only mock boundaries: network, storage, time)
- Forget to restore mocks/timers
- Use
any types in tests
- Create brittle tests tied to DOM structure
- Add backward-compatibility hacks for test utilities
Advanced Topics
For deeper dives, see the ./references/ directory:
TESTING_PATTERNS.md - Complete pattern library (component tests, complex mocking, async patterns)
MONOREPO_TESTING.md - Workspace-specific strategies (shared vs. app tests, path aliases, organization)
TROUBLESHOOTING.md - Debug guide (common errors, performance, coverage, CI/CD)
Coverage Analysis
To add coverage:
export default defineConfig({
test: {
coverage: {
provider: "v8",
reporter: ["text", "html", "json"],
exclude: ["**/*.test.ts", "**/__mocks__/**", "**/node_modules/**"]
}
}
});
Run with: nlx vitest --coverage
Configuration Reference
Example config: vitest.config.ts
{
environment: "jsdom",
globals: true,
include: ["**/*.test.{js,ts,tsx}"],
exclude: ["**/node_modules/**", "**/e2e/**"],
setupFiles: ["./tests/setup.ts"],
alias: {
"@": "./src",
},
}
Next Steps
- For Zustand store testing - Activate
zustand skill
- For component testing - See
./references/TESTING_PATTERNS.md (React Testing Library setup)
- For monorepo-specific strategies - See
./references/MONOREPO_TESTING.md
- For debugging help - See
./references/TROUBLESHOOTING.md
Start with simple unit tests, add component tests as needed.