Разработка Unit-тестов для фронтенда (Vitest)
Vitest — тест-раннер нового поколения от команды Vite. Использует ту же конфигурацию, что и Vite-проект, поддерживает нативный ESM, на порядок быстрее Jest на больших проектах. Выбор по умолчанию для Vite, Nuxt 3, SvelteKit-проектов.
Настройка
npm install -D vitest @vitest/ui jsdom @testing-library/react @testing-library/jest-dom
// vite.config.ts (или vitest.config.ts)
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./src/test/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
thresholds: { lines: 80, functions: 80, branches: 70 },
},
},
});
// src/test/setup.ts
import '@testing-library/jest-dom';
import { cleanup } from '@testing-library/react';
import { afterEach } from 'vitest';
afterEach(() => cleanup());
Синтаксис идентичен Jest
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { formatDate } from '../utils/date';
describe('formatDate', () => {
it('formats ISO date to Russian locale', () => {
const date = new Date('2024-11-15T10:00:00Z');
expect(formatDate(date, 'ru-RU')).toBe('15 ноября 2024 г.');
});
it('returns dash for null', () => {
expect(formatDate(null)).toBe('—');
});
});
Тестирование компонентов
import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import userEvent from '@testing-library/user-event';
import { SearchInput } from './SearchInput';
describe('SearchInput', () => {
it('calls onSearch after debounce', async () => {
vi.useFakeTimers();
const onSearch = vi.fn();
render(<SearchInput onSearch={onSearch} debounce={300} />);
await userEvent.type(screen.getByRole('searchbox'), 'ноутбук');
expect(onSearch).not.toHaveBeenCalled(); // debounce ещё не прошёл
vi.advanceTimersByTime(300);
expect(onSearch).toHaveBeenCalledWith('ноутбук');
vi.useRealTimers();
});
it('clears input on Escape', async () => {
render(<SearchInput onSearch={vi.fn()} />);
const input = screen.getByRole('searchbox');
await userEvent.type(input, 'text');
await userEvent.keyboard('{Escape}');
expect(input).toHaveValue('');
});
});
Моки и шпионы
// vi.mock — замена модуля
vi.mock('../services/api', () => ({
getProducts: vi.fn().mockResolvedValue([
{ id: 1, name: 'MacBook', price: 150000 },
]),
}));
// vi.spyOn — шпион за методом
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
Concurrent-тесты (отличие от Jest)
// Vitest поддерживает параллельные тесты внутри describe
describe.concurrent('parallel tests', () => {
it.concurrent('test 1', async () => { ... });
it.concurrent('test 2', async () => { ... });
});
UI-режим
# Интерактивный браузерный UI
npx vitest --ui
# Открывает http://localhost:51204/__vitest__/
# Показывает дерево тестов, coverage, diff провальных тестов
Preformance vs Jest
На проекте с 500+ тестами Vitest обычно в 2–5 раз быстрее Jest благодаря:
- Нативному ESM без трансформации в CJS
- Общему dev-серверу с основным Vite-процессом
- Watch mode с HMR для тестов
Срок реализации
Настройка Vitest + миграция с Jest (если нужна) + написание первого пакета тестов: 2–4 дня.







