| name | vitest |
| description | JavaScript/TypeScript unit testing with Vitest — fast Vite-native test runner with Jest-compatible API. Use when writing or running tests in a Vite-based project (React, Vue, Svelte, vanilla TS/JS), migrating from Jest, benchmarking code, testing browser APIs with jsdom/happy-dom, or needing in-source testing. Native ES modules, TypeScript, and JSX support with zero config. Pairs with test-driven-development for the methodology. |
Vitest — Fast Vite-Native Testing
Overview
Vitest is a blazing-fast JavaScript/TypeScript test runner powered by Vite. It shares Vite's configuration and plugin pipeline, supports ES modules natively, and has a Jest-compatible API — migrate from Jest with minimal changes. This skill is the tool reference; for the red-green-refactor methodology see [[test-driven-development]].
Installation
npm install --save-dev vitest
npm install --save-dev @vitest/ui
npm install --save-dev @vitest/browser @vitest/browser-playwright
npm install --save-dev @vitest/coverage-v8
Add to package.json:
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage"
}
}
Configuration
import { defineConfig } from 'vite';
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test-setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
thresholds: { lines: 80 },
},
},
});
Or a standalone vitest.config.ts:
import { defineConfig } from 'vitest/config';
export default defineConfig({ test: { globals: true } });
Running Tests
vitest
vitest run
vitest --ui
vitest --coverage
vitest src/utils
vitest -t "user"
vitest bench
Test Structure (Jest-compatible API)
import { describe, it, expect, beforeEach } from 'vitest';
import { add, createUser } from './utils';
describe('add', () => {
it('adds two numbers', () => {
expect(add(2, 3)).toBe(5);
});
});
describe('createUser', () => {
it('creates a user with default role', () => {
const user = createUser('Alice');
expect(user).toEqual({ name: 'Alice', role: 'user' });
});
});
With globals: true in config, you can omit imports:
test('works without imports', () => {
expect(1 + 1).toBe(2);
});
Mocking
import { vi } from 'vitest';
vi.mock('./api', () => ({
fetchUser: vi.fn().mockResolvedValue({ id: 1, name: 'Alice' }),
}));
const spy = vi.spyOn(console, 'warn').mockImplementation(() => {});
expect(spy).toHaveBeenCalledWith('expected warning');
spy.mockRestore();
vi.useFakeTimers();
vi.advanceTimersByTime(1000);
vi.useRealTimers();
vi.stubEnv('NODE_ENV', 'test');
vi.unstubAllEnvs();
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ json: () => ({ ok: true }) }));
Async Tests
it('fetches data', async () => {
const data = await fetchData();
expect(data).toMatchObject({ status: 'ok' });
});
await expect(fetchUser(1)).resolves.toHaveProperty('name');
await expect(fetchUser(-1)).rejects.toThrow('Not found');
Snapshot Testing
import { renderToString } from 'react-dom/server';
it('matches snapshot', () => {
const result = renderToString(<Component />);
expect(result).toMatchSnapshot();
});
expect(add(1, 2)).toMatchInlineSnapshot('3');
Update snapshots: vitest -u or vitest --update-snapshots
In-Source Testing
Vitest supports placing tests directly inside source files:
export function add(a: number, b: number) { return a + b; }
if (import.meta.vitest) {
const { it, expect } = import.meta.vitest;
it('adds', () => expect(add(1, 2)).toBe(3));
}
Enable in config: test: { includeSource: ['src/**/*.ts'] }. For production builds you must
also strip the test block so it never ships — define import.meta.vitest as undefined so
the bundler can dead-code-eliminate it:
export default defineConfig({
define: { 'import.meta.vitest': 'undefined' },
});
Benchmarks
import { bench, describe } from 'vitest';
describe('sort algorithms', () => {
bench('native sort', () => {
[3, 1, 2].sort();
});
bench('custom sort', () => {
customSort([3, 1, 2]);
});
});
Run: vitest bench
Browser Mode (Real Browser Testing)
import { defineConfig } from 'vitest/config';
import { playwright } from '@vitest/browser-playwright';
export default defineConfig({
test: {
browser: {
enabled: true,
provider: playwright(),
instances: [
{ browser: 'chromium' },
],
},
},
});
Migrating from Jest
Vitest's API is intentionally Jest-compatible. Key differences:
- Replace
jest global with vi (jest.fn() → vi.fn(), jest.mock() → vi.mock())
jest.useFakeTimers() → vi.useFakeTimers()
jest.spyOn() → vi.spyOn()
- Import from
'vitest' instead of @jest/globals
- Remove
jest.config.* and add test:{} to vite.config.ts
grep -r "jest\." src --include="*.test.*"
Coverage
vitest run --coverage
test: {
coverage: {
provider: 'v8',
include: ['src/**/*.ts'],
exclude: ['src/**/*.d.ts', 'src/test-*'],
thresholds: { statements: 80, branches: 70, lines: 80 },
},
}
Type Checking
Vitest doesn't type-check by default (Vite strips types). Use alongside:
tsc --noEmit
Related Skills
- [[test-driven-development]] — TDD methodology and workflow
- [[jest]] — Jest testing (for non-Vite projects)
- [[playwright-best-practices]] — End-to-end browser testing
- [[playwright-cli]] — Browser automation from CLI