npm install -D vitest | Install Vitest |
npx vitest | Run tests (watch mode) |
npx vitest run | Run tests once |
npx vitest --ui | Open UI |
npx vitest --coverage | Run with coverage |
npx vitest --reporter=verbose | Verbose output |
npx vitest related src/file.ts | Run related tests |
npx vitest bench | Run benchmarks |
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
include: ['**/*.{test,spec}.{js,ts,jsx,tsx}'],
exclude: ['node_modules', 'dist'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
},
},
}); // vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
},
}); import { describe, it, expect, test } from 'vitest';
describe('Math operations', () => {
it('adds numbers', () => {
expect(1 + 1).toBe(2);
});
test('multiplies numbers', () => {
expect(2 * 3).toBe(6);
});
it.skip('skipped test', () => {});
it.only('only this test runs', () => {});
it.todo('implement later');
}); describe('User', () => {
describe('when logged in', () => {
it('shows dashboard', () => {
// ...
});
});
describe('when logged out', () => {
it('shows login page', () => {
// ...
});
});
}); import { describe, it, expect } from 'vitest';
describe.each([
{ a: 1, b: 1, expected: 2 },
{ a: 1, b: 2, expected: 3 },
{ a: 2, b: 1, expected: 3 },
])('add($a, $b)', ({ a, b, expected }) => {
it(`returns ${expected}`, () => {
expect(a + b).toBe(expected);
});
});
it.each([
[1, 1, 2],
[1, 2, 3],
[2, 1, 3],
])('add(%i, %i) -> %i', (a, b, expected) => {
expect(a + b).toBe(expected);
}); expect(value).toBe(expected); // strict equality
expect(value).toEqual(expected); // deep equality
expect(value).toStrictEqual(expected); // strict deep equality
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();
expect(value).toBeNaN(); expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(3.5);
expect(value).toBeLessThan(5);
expect(value).toBeLessThanOrEqual(4.5);
expect(0.1 + 0.2).toBeCloseTo(0.3, 5); expect(string).toMatch(/pattern/);
expect(string).toContain('substring');
expect(string).toHaveLength(10); expect(array).toContain(item);
expect(array).toContainEqual({ a: 1 });
expect(array).toHaveLength(3);
expect(object).toHaveProperty('key');
expect(object).toHaveProperty('key', value);
expect(object).toMatchObject({ key: value }); expect(() => fn()).toThrow();
expect(() => fn()).toThrow(Error);
expect(() => fn()).toThrow('error message');
expect(() => fn()).toThrow(/pattern/);
expect(promise).rejects.toThrow();
await expect(asyncFn()).rejects.toThrow('error'); import { beforeAll, afterAll, beforeEach, afterEach } from 'vitest';
beforeAll(async () => {
// Run once before all tests
await setupDatabase();
});
afterAll(async () => {
// Run once after all tests
await cleanupDatabase();
});
beforeEach(() => {
// Run before each test
resetState();
});
afterEach(() => {
// Run after each test
cleanup();
}); // src/test/setup.ts
import { vi } from 'vitest';
import '@testing-library/jest-dom/vitest';
// Mock global
globalThis.fetch = vi.fn();
// Reset mocks before each test
beforeEach(() => {
vi.clearAllMocks();
});
// Cleanup after each test
afterEach(() => {
vi.useRealTimers();
}); import { vi, expect } from 'vitest';
const fn = vi.fn();
fn('arg1', 'arg2');
expect(fn).toHaveBeenCalled();
expect(fn).toHaveBeenCalledTimes(1);
expect(fn).toHaveBeenCalledWith('arg1', 'arg2');
expect(fn).toHaveBeenLastCalledWith('arg1', 'arg2'); const fn = vi.fn();
fn.mockReturnValue('default');
fn.mockReturnValueOnce('first');
fn.mockResolvedValue('async result');
fn.mockResolvedValueOnce('first async');
fn.mockRejectedValue(new Error('fail'));
fn.mockImplementation((x) => x * 2);
fn.mockImplementationOnce((x) => x * 3); const obj = {
method: (x: number) => x * 2,
};
const spy = vi.spyOn(obj, 'method');
spy.mockReturnValue(10);
obj.method(5); // returns 10
expect(spy).toHaveBeenCalledWith(5);
spy.mockRestore(); // restore original import { vi } from 'vitest';
vi.mock('./utils', () => ({
fetchData: vi.fn(() => Promise.resolve({ data: 'mocked' })),
helper: vi.fn(),
}));
// Partial mock
vi.mock('./utils', async (importOriginal) => {
const actual = await importOriginal();
return {
...actual,
fetchData: vi.fn(),
};
}); vi.mock('axios', () => ({
default: {
get: vi.fn(() => Promise.resolve({ data: {} })),
post: vi.fn(),
},
}));
// In test
import axios from 'axios';
it('fetches data', async () => {
vi.mocked(axios.get).mockResolvedValue({ data: { id: 1 } });
// ...
}); // vi.hoisted ensures mock is defined before imports
const mockFetch = vi.hoisted(() => vi.fn());
vi.mock('./api', () => ({
fetchData: mockFetch,
}));
import { fetchData } from './api';
it('uses mock', () => {
mockFetch.mockResolvedValue({ data: 'test' });
// ...
}); import { vi, beforeEach, afterEach } from 'vitest';
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('handles setTimeout', () => {
const fn = vi.fn();
setTimeout(fn, 1000);
expect(fn).not.toHaveBeenCalled();
vi.advanceTimersByTime(1000);
expect(fn).toHaveBeenCalled();
});
it('handles intervals', () => {
const fn = vi.fn();
setInterval(fn, 100);
vi.advanceTimersByTime(350);
expect(fn).toHaveBeenCalledTimes(3);
}); vi.useFakeTimers();
vi.advanceTimersByTime(1000); // Advance by ms
vi.advanceTimersToNextTimer(); // Run next timer
vi.runAllTimers(); // Run all pending
vi.runOnlyPendingTimers(); // Run only pending
vi.setSystemTime(new Date(2023, 0, 1));
vi.getMockedSystemTime(); // Get mocked time
vi.useRealTimers(); // Restore real timers it('fetches data', async () => {
const data = await fetchData();
expect(data).toEqual({ id: 1 });
});
it('handles errors', async () => {
await expect(fetchBadData()).rejects.toThrow('Not found');
}); it('resolves', () => {
return expect(fetchData()).resolves.toEqual({ id: 1 });
});
it('rejects', () => {
return expect(fetchBadData()).rejects.toThrow();
}); it('calls callback', () => {
return new Promise((resolve) => {
doSomethingAsync((result) => {
expect(result).toBe('done');
resolve();
});
});
}); it('matches snapshot', () => {
const tree = render(<Button>Click me</Button>);
expect(tree).toMatchSnapshot();
});
it('matches inline snapshot', () => {
const user = getUser();
expect(user).toMatchInlineSnapshot(`
{
"id": 1,
"name": "John",
}
`);
}); // CLI
npx vitest -u
npx vitest --update
// In watch mode, press 'u' to update // vitest.config.ts
export default defineConfig({
test: {
coverage: {
provider: 'v8', // or 'istanbul'
reporter: ['text', 'json', 'html', 'lcov'],
reportsDirectory: './coverage',
include: ['src/**/*.{js,ts}'],
exclude: [
'node_modules',
'src/**/*.test.{js,ts}',
'src/**/*.d.ts',
],
all: true,
lines: 80,
functions: 80,
branches: 80,
statements: 80,
},
},
}); npx vitest --coverage
npx vitest --coverage.enabled --coverage.provider=v8 import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Button } from './Button';
it('renders button', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button')).toBeInTheDocument();
});
it('handles click', async () => {
const user = userEvent.setup();
const onClick = vi.fn();
render(<Button onClick={onClick}>Click</Button>);
await user.click(screen.getByRole('button'));
expect(onClick).toHaveBeenCalledTimes(1);
}); // Get (throws if not found)
screen.getByRole('button');
screen.getByText('Hello');
screen.getByLabelText('Email');
screen.getByPlaceholderText('Search');
screen.getByTestId('submit-btn');
// Query (returns null)
screen.queryByRole('button');
// Find (async, waits)
await screen.findByRole('button');
// Multiple
screen.getAllByRole('listitem');