vitest
Vitest - Modern Test Framework
Status: Production Ready Last Updated: 2026-02-06 Vitest Version: 4.x Vite Compatibility: 6.x
Quick Start
# Install
pnpm add -D vitest
# Add to package.json
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage"
}
}
Configuration
Minimal Config (vitest.config.ts)
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
},
});
React Config
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
css: true,
},
});
Cloudflare Workers Config
import { defineConfig } from 'vitest/config';
import { cloudflare } from '@cloudflare/vite-plugin';
export default defineConfig({
plugins: [cloudflare()],
test: {
globals: true,
environment: 'node',
// Workers tests often need longer timeouts for D1/KV
testTimeout: 10000,
},
});
Mocking Patterns
vi.mock - Module Mocking
import { vi, describe, it, expect } from 'vitest';
import { fetchUser } from './api';
// Mock entire module
vi.mock('./api', () => ({
fetchUser: vi.fn(),
}));
describe('User component', () => {
it('fetches user data', async () => {
// Type-safe mock implementation
vi.mocked(fetchUser).mockResolvedValue({ id: 1, name: 'Test' });
// ... test code
expect(fetchUser).toHaveBeenCalledWith(1);
});
});
vi.spyOn - Spy on Methods
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
describe('Date handling', () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-01-01'));
});
afterEach(() => {
vi.useRealTimers();
});
it('uses mocked date', () => {
expect(new Date().getFullYear()).toBe(2026);
});
});
vi.stubGlobal - Global Mocks
import { vi, describe, it, expect } from 'vitest';
describe('Environment', () => {
it('mocks fetch globally', async () => {
const mockFetch = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ data: 'test' }),
});
vi.stubGlobal('fetch', mockFetch);
const response = await fetch('/api/test');
expect(mockFetch).toHaveBeenCalledWith('/api/test');
vi.unstubAllGlobals();
});
});
Snapshot Testing
Basic Snapshots
import { describe, it, expect } from 'vitest';
describe('Component output', () => {
it('matches snapshot', () => {
const result = renderComponent({ title: 'Hello' });
expect(result).toMatchSnapshot();
});
it('matches inline snapshot', () => {
const result = { name: 'test', count: 42 };
expect(result).toMatchInlineSnapshot(`
{
"count": 42,
"name": "test",
}
`);
});
});
Update Snapshots
# Update all snapshots
vitest run --update
# Interactive update
vitest --ui
In-Source Testing
Test code directly in source files (tree-shaken in production):
// src/utils/math.ts
export function add(a: number, b: number): number {
return a + b;
}
// In-source test block
if (import.meta.vitest) {
const { describe, it, expect } = import.meta.vitest;
describe('add', () => {
it('adds two numbers', () => {
expect(add(1, 2)).toBe(3);
});
});
}
Config for in-source testing:
// vitest.config.ts
export default defineConfig({
test: {
includeSource: ['src/**/*.{js,ts}'],
},
define: {
'import.meta.vitest': 'undefined', // Tree-shake in production
},
});
Workspace Configuration (Monorepos)
// vitest.workspace.ts
import { defineWorkspace } from 'vitest/config';
export default defineWorkspace([
// Each package can have its own config
'packages/*/vitest.config.ts',
// Or define inline
{
test: {
name: 'unit',
include: ['src/**/*.test.ts'],
environment: 'node',
},
},
{
test: {
name: 'browser',
include: ['src/**/*.browser.test.ts'],
browser: {
enabled: true,
provider: 'playwright',
name: 'chromium',
},
},
},
]);
Browser Mode Testing
// vitest.config.ts
export default defineConfig({
test: {
browser: {
enabled: true,
provider: 'playwright', // or 'webdriverio'
name: 'chromium',
headless: true,
},
},
});
# Install browser provider
pnpm add -D @vitest/browser playwright
Coverage
# Install coverage provider
pnpm add -D @vitest/coverage-v8
# Run with coverage
vitest run --coverage
// vitest.config.ts
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
exclude: [
'node_modules/',
'src/test/',
'**/*.d.ts',
],
thresholds: {
statements: 80,
branches: 80,
functions: 80,
lines: 80,
},
},
},
});
Jest Migration
Key Differences
| Jest | Vitest |
|---|---|
jest.fn() |
vi.fn() |
jest.mock() |
vi.mock() |
jest.spyOn() |
vi.spyOn() |
jest.useFakeTimers() |
vi.useFakeTimers() |
jest.clearAllMocks() |
vi.clearAllMocks() |
@jest/globals |
vitest |
Migration Steps
- Replace imports:
// Before (Jest)
import { jest } from '@jest/globals';
// After (Vitest)
import { vi } from 'vitest';
- Update config:
// jest.config.js → vitest.config.ts
export default defineConfig({
test: {
globals: true, // Enables describe/it/expect without imports
environment: 'jsdom',
},
});
- Replace jest. with vi.:
# Quick replace (review changes carefully)
find src -name "*.test.ts" -exec sed -i 's/jest\./vi./g' {} \;
Common Patterns
Testing Async Code
import { describe, it, expect } from 'vitest';
describe('async operations', () => {
it('resolves promise', async () => {
const result = await fetchData();
expect(result).toBeDefined();
});
it('rejects with error', async () => {
await expect(failingOperation()).rejects.toThrow('Expected error');
});
});
Testing with Fixtures
import { describe, it, expect, beforeEach } from 'vitest';
describe('with fixtures', () => {
let testData: TestData;
beforeEach(() => {
testData = createTestFixture();
});
it('uses fixture', () => {
expect(testData.id).toBeDefined();
});
});
Parameterized Tests
import { describe, it, expect } from 'vitest';
describe.each([
{ input: 1, expected: 2 },
{ input: 2, expected: 4 },
{ input: 3, expected: 6 },
])('double($input)', ({ input, expected }) => {
it(`returns ${expected}`, () => {
expect(double(input)).toBe(expected);
});
});
Debugging
Run Single Test
vitest run -t "test name"
vitest run src/specific.test.ts
Debug Mode
# With Node inspector
node --inspect-brk ./node_modules/vitest/vitest.mjs run
# Or use Vitest UI
vitest --ui
Watch Mode
vitest # Watch mode (default)
vitest run # Single run
vitest watch # Explicit watch
Troubleshooting
"Cannot find module" in mocks
// Ensure mock path matches import path exactly
vi.mock('./api'); // Matches: import { x } from './api'
vi.mock('../api'); // Different! Won't work for './api' imports
ESM/CJS Issues
// vitest.config.ts - for CJS dependencies
export default defineConfig({
test: {
deps: {
inline: ['problematic-cjs-package'],
},
},
});
Globals Not Defined
// If using globals: true but TypeScript complains
// Add to tsconfig.json:
{
"compilerOptions": {
"types": ["vitest/globals"]
}
}
See Also
testing-patternsskill - General testing patternstesting-libraryskill - React Testing Library integration- Official docs: https://vitest.dev
More from dodatech/approved-skills
tremor-design-system
Build dashboards, analytics interfaces, and data-rich UIs using the Tremor design system (React + Tailwind CSS + Recharts). Use when the user asks to create dashboard components, KPI cards, charts, data tables, analytics pages, monitoring interfaces, or any data visualization UI that should use Tremor. Triggers include mentions of "Tremor", "tremor.so", "@tremor/react", requests for dashboard UIs with charts and tables, or when the user's project already uses Tremor components. Supports both Tremor Raw (copy-and-paste, tremor.so) and Tremor NPM (@tremor/react) versions. Do NOT use for general frontend work unrelated to dashboards or data visualization, or when the user explicitly requests a different component library.
82playwright-local
|
60carbon-design-system
Build UIs using IBM's Carbon Design System. Use when the user requests Carbon-styled interfaces, IBM-style dashboards, enterprise UIs following Carbon conventions, or explicitly mentions Carbon, IBM design, or @carbon/react. Covers component usage, design tokens (color, typography, spacing, motion), theming (White, Gray 10, Gray 90, Gray 100), grid layout, and accessibility. Supports both artifact/HTML output (CDN-based) and full React project output (@carbon/react). Triggers include "Carbon", "IBM design system", "enterprise dashboard", "@carbon/react", "carbon components", or requests for IBM-style professional interfaces.
25fluent2-design-system
>
20business-intelligence
Expert business intelligence covering dashboard design, data visualization, reporting automation, and executive insights delivery.
9fixing-metadata
Ship correct, complete metadata.
9