Unit тестирование с Vitest — это не просто ещё одна библиотека для проверки кода, это удобный инструмент, который делает процесс тестирования живым и предсказуемым. В этой статье я расскажу, как начать работать с Vitest, какие приёмы ускоряют разработку и на что стоит обратить внимание при переходе от других тест-раннеров. Материал рассчитан на практиков: примеры, советы и реальные подводные камни из моего опыта внедрения тестов в проектах на Vue и React.
Почему стоит выбрать Vitest
Vitest возник как логичное продолжение экосистемы Vite: он быстрый, поддерживает модульную систему ESM и работает почти как родной инструмент для современных фронтенд-проектов. В отличие от некоторых более тяжёлых решений, Vitest загружает тесты быстро и в режиме наблюдения реагирует на изменения без лишней перезагрузки окружения.
По личным наблюдениям, команда привыкает к Vitest быстрее, чем к альтернативам: конфиг прост, API знакомо выглядит, а инструменты для мокирования и асинхронных сценариев покрывают большую часть повседневных задач. Это не значит, что у него нет ограничений, но многие из них легко обойти при грамотной настройке.
Установка и базовая настройка
Установить Vitest можно парой команд, если ваш проект уже использует npm или yarn. Для большинства случаев достаточно добавить пакет и настроить скрипты в package.json. В проектах на Vite подключение происходит быстро, а для других сборщиков доступно несколько примеров конфигураций.
Ниже приведены минимальные шаги для установки в проект с npm.
npm install -D vitest @vitest/ui
# или
yarn add -D vitest @vitest/ui
Далее в package.json добавьте скрипты для запуска тестов и режима наблюдения. Для интеграции с Vite можно использовать отдельный конфиг или поместить настройки в vite.config.ts, если требуется специфическая трансформация модулей.
Ключевые возможности и API
Основные конструкции Vitest знакомы разработчикам, привыкшим к Jest: функции describe, it, expect работают ожидаемо. Кроме этого есть встроенные утилиты для мокирования (vi), управление таймерами и запуск тестов в режиме watch, что ускоряет цикл разработки.
Вот краткий список того, что будет использоваться чаще всего:
- describe / it / test — структура и сами тестовые случаи;
- expect — ассерты с богатым набором матчеров;
- vi.fn, vi.spyOn, vi.mock — средства для мокирования и контроля вызовов;
- fake timers и контроль времени для тестирования debounce/throttle;
- snapshot-тестирование и запуск в режиме watch для быстрого обратного отклика.
Пишем первые юнит-тесты: пример на функции
Для начала рассмотрим простой пример: утилитная функция, которая форматирует строки. Тесты помогут зафиксировать поведение при граничных значениях и при изменении кода в будущем. Пример ниже показывает структуру файла и базовые проверки.
// format.js
export function capitalize(str) {
if (!str) return '';
return str[0].toUpperCase() + str.slice(1);
}
// format.test.js
import { describe, it, expect } from 'vitest';
import { capitalize } from './format';
describe('capitalize', () => {
it('преобразует первую букву в заглавную', () => {
expect(capitalize('hello')).toBe('Hello');
});
it('возвращает пустую строку при пустом вводе', () => {
expect(capitalize('')).toBe('');
});
});
Такой подход помогает быстро обнаружить регрессии и делает код удобным для рефакторинга. В реальном проекте тесты пишутся рядом с модулями, это упрощает навигацию и поддержку.
Мокирование и изоляция: vi в действии
В проектах часто нужно изолировать модуль от внешних зависимостей: HTTP-запросов, локального хранилища или больших утилит. Vitest предоставляет vi.mock и vi.fn, которые работают похоже на привычные мок-объекты, но учитывают особенности ESM. Это делает моки гибкими и предсказуемыми.
Пример использования vi.mock для подмены HTTP-клиента.
// api.js
export async function fetchUser(id) {
const res = await fetch(`/users/${id}`);
return res.json();
}
// api.test.js
import { describe, it, expect, vi } from 'vitest';
import { fetchUser } from './api';
vi.stubGlobal('fetch', vi.fn(() =>
Promise.resolve({ json: () => ({ id: 1, name: 'Anna' }) })
));
describe('fetchUser', () => {
it('возвращает данные пользователя', async () => {
const user = await fetchUser(1);
expect(user.name).toBe('Anna');
});
});
Важно аккуратно управлять состоянием глобальных моков между тестами, чтобы не получить ложные срабатывания. У меня в старых проектах одна забытая глобальная подмена приводила к неочевидным тестовым сбоям — поэтому всегда очищаю моки в afterEach.
Асинхронный код и контроль времени
Тестирование асинхронных функций и таймеров — частая боль, особенно когда используется setTimeout, debounce или polling. Vitest умеет работать с fake timers, позволяя продвигать время вперёд и проверять поведение без реальных задержек. Это экономит время при запуске набора тестов и делает результаты стабильными.
Пример использования fake timers для debounce-функции.
import { vi, describe, it, expect, afterEach } from 'vitest';
import { debounce } from './debounce';
describe('debounce', () => {
afterEach(() => {
vi.useRealTimers();
});
it('вызов колбека один раз при быстрой серии событий', () => {
vi.useFakeTimers();
const fn = vi.fn();
const deb = debounce(fn, 200);
deb();
deb();
deb();
vi.advanceTimersByTime(200);
expect(fn).toHaveBeenCalledTimes(1);
});
});
Такой тест надёжен и не зависит от реального времени. При использовании fake timers важно возвращать реальное время в afterEach, чтобы не нарушить другие тесты.
Интеграция с CI и рекомендации по структуре
Автоматический запуск тестов в CI — обязательный элемент зрелого процесса. Vitest легко интегрируется в популярные CI-системы: достаточно вызвать npm run test или настроить специфичные флаги для запуска в headless-режиме. Для ускорения сборки тестов применяйте параллелизм и кеширование, когда это возможно.
Ниже — краткий перечень рекомендаций по организации тестовой базы и CI.
- разделяйте unit и интеграционные тесты по папкам для гибкого запуска;
- используйте теги или паттерны файлов, чтобы запускать критичные тесты в PR-ах;
- чистите и сбрасывайте глобальное состояние между тестами;
- включайте отчёты покрытия кода и отслеживайте их динамику в CI.
Типичные ошибки и как их избежать
Частая ошибка — тестирование реализации вместо поведения. Это приводит к ломким тестам, которые падают при рефакторинге, хотя логика не изменилась. Лучше проверять внешнее поведение модуля и его контракты, а не внутренние детали.
Ещё одна проблема — смешение unit и интеграционных тестов в одном наборе. Если тест зависит от реального сетевого слоя или базы данных, он перестаёт быть быстрым и надёжным. Для таких случаев лучше явно пометить тесты и запускать их отдельно в CI.
Мой опыт внедрения Vitest в рабочий проект
В одном из проектов мы начали с миграции небольшого набора утилит и компонентов на Vitest, чтобы проверить, как команда воспримет новое окружение. Первые несколько дней были посвящены настройке конфигурации и написанию шаблонов для тестов, затем процесс пошёл быстрее и продуктивность выросла.
Практическая польза проявилась через пару недель: баги при рефакторинге начали обнаруживаться на ранних этапах, ревью стали короче, а разработчики стали реже бояться правок в критических модулях. Накопленный опыт показал, что важнее стабильные, небольшие тесты, чем громоздкие сценарии с множеством зависимостей.
Краткие практические советы
Ниже собраны полезные приёмы, которые сэкономят время при ежедневной работе с Vitest.
| Задача | Рекомендация |
|---|---|
| Быстрый feedback | Используйте режим watch с фильтрацией по имени теста |
| Изоляция | Мокируйте сетевые запросы и внешние модули |
| Надёжность | Очистка стора и восстановление таймеров в afterEach |
И ещё одно наблюдение: не старайтесь покрыть все подряд мелкие геттеры тестами сразу, начните с критичных сценариев и добавляйте покрытия туда, где вероятность регрессии высока. Такой подход экономит время и увеличивает отдачу от тестов.
Vitest — современный инструмент, который укладывается в привычные рабочие процессы и даёт гибкость, нужную для реальных проектов. Он не идеален, но сочетание скорости, простоты и совместимости с ESM делает его отличным выбором для большинства фронтенд-команд.
Если вы только начинаете или планируете миграцию наборов тестов, начните с небольшого пилотного набора, настройте CI и постепенно расширяйте покрытие, опираясь на реальные баги и точки роста в проекте. Такой путь позволит избежать перегрузки команды и получить ощутимую пользу от тестирования в короткие сроки.

