Vitest — тестовый раннер, который использует тот же конвейер преобразований, что и Vite. Он понимает ESM и TypeScript без дополнительной настройки, запускает тесты в пуле воркеров и умеет работать в режиме наблюдения, перезапуская только затронутые файлы. Для проекта на Vite это самый короткий путь от «тестов нет» до покрытия ключевых модулей. Ниже — установка, конфигурация, мокинг, покрытие и ошибки, которые встречаются чаще всего.

Зачем Vitest рядом с Vite

Старые проекты часто несут Jest с отдельным транспайлером, маппингом модулей и набором плагинов. Vitest переиспользует конфигурацию Vite: алиасы, плагины и обработку CSS он берёт из того же места, поэтому тестовое окружение перестаёт отличаться от сборки. Это снижает класс ошибок «в тестах работает, в браузере нет».

API при этом знаком: describe, it, expect, beforeEach и мок-функции выглядят почти как в Jest. Переезд обычно сводится к замене импортов и настройке окружения. Ограничение тоже есть: Vitest рассчитан на проекты, где уже есть Vite или где допустимо его добавить.

Установка и первый тест

Проще всего начать с шаблона Vite, а затем добавить раннер и покрытие. Пакет jsdom нужен только для тестов, которые работают с DOM; для серверного кода достаточно окружения node.

npm create vite@latest vitest-demo -- --template react-ts
cd vitest-demo
npm install
npm install -D vitest @vitest/coverage-v8 jsdom

Затем добавьте скрипты. Режим наблюдения удобен локально, а vitest run обязателен в CI: он завершается с ненулевым кодом при падении и не ждёт изменений файлов.

{
  "scripts": {
    "test": "vitest",
    "test:run": "vitest run",
    "coverage": "vitest run --coverage"
  }
}

Первый тест кладите рядом с модулем: src/lib/price.ts и src/lib/price.test.ts. Такое соседство упрощает навигацию и позволяет команде vitest related точно сопоставлять изменённый файл с его тестами.

Конфигурация

Отдельный vitest.config.ts подходит, когда тестовое окружение отличается от сборки. Если в проекте уже есть vite.config.ts, добавьте секцию test прямо в него и подключите типы через /// <reference types="vitest/config" />, иначе TypeScript не увидит новое поле. Альтернатива — собрать конфиг функцией mergeConfig из пакета vite.

import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: ['./src/test/setup.ts'],
    include: ['src/**/*.{test,spec}.{ts,tsx}'],
    restoreMocks: true,
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html', 'lcov'],
      thresholds: { lines: 80, functions: 80, branches: 70, statements: 80 },
      exclude: ['src/main.tsx', '**/*.d.ts'],
    },
  },
})

Флаг globals избавляет от импорта функций в каждом файле, но тогда в tsconfig.json нужно указать "types": ["vitest/globals"]. Без этого редактор будет подчёркивать describe как неизвестное имя, хотя тесты запустятся. Файл setupFiles подключается до каждого тестового модуля: там место для полифилов, расширений expect и глобальной очистки. Создайте его, даже если он пока пуст.

Тесты, параметризация и мокинг

Проверяйте поведение, а не реализацию. Если тест ломается от переименования внутренней переменной, он написан неправильно. Для набора однотипных случаев используйте it.each — так таблица значений остаётся рядом с проверкой.

import { describe, it, expect } from 'vitest'
import { formatPrice } from './price'

describe('formatPrice', () => {
  it('добавляет разделитель разрядов', () => {
    expect(formatPrice(1500)).toBe('1 500 ₽')
  })

  it.each([[0, '0 ₽'], [99, '99 ₽'], [1000, '1 000 ₽']])(
    '%i превращается в %s',
    (input, expected) => {
      expect(formatPrice(input)).toBe(expected)
    },
  )
})

Внешние зависимости заменяются через vi.fn и vi.stubGlobal. Важно возвращать окружение в исходное состояние: restoreMocks в конфиге снимает шпионов, но глобальные подмены нужно убирать явно в afterEach.

import { it, expect, vi, afterEach } from 'vitest'
import { loadUser } from './api'

afterEach(() => vi.unstubAllGlobals())

it('разбирает ответ API', async () => {
  const fetchMock = vi.fn().mockResolvedValue({
    ok: true,
    json: async () => ({ id: 7, name: 'Анна' }),
  })
  vi.stubGlobal('fetch', fetchMock)

  await expect(loadUser(7)).resolves.toEqual({ id: 7, name: 'Анна' })
  expect(fetchMock).toHaveBeenCalledWith('/api/users/7')
})

Таймеры тестируются виртуальными часами: vi.useFakeTimers() перехватывает setTimeout и setInterval, а advanceTimersByTime прокручивает время без реального ожидания. Не забудьте vi.useRealTimers() в конце, иначе следующий тест может зависнуть на ожидании промиса.

Покрытие и CI

Порог покрытия полезен как защита от регрессии, но только вместе с осмысленными тестами. Сто процентов покрытия по строкам легко получить, вызывая код без единой проверки. Начните с 70–80 процентов по ключевым модулям и повышайте планку постепенно.

npx vitest run --coverage
npx vitest run --reporter=default --reporter=junit --outputFile=reports/junit.xml
npx vitest related src/lib/price.ts
npx vitest run --coverage --silent

В конвейере отчёт junit подхватывают системы CI, а --silent убирает вывод console.log из логов, не отключая сами тесты. Если сборка большая, разделите её на независимые задания и запускайте тесты по каталогам — это проще, чем пытаться распараллелить один процесс.

Типичные ошибки

  • Тесты падают на импорте CSS или картинок — проверьте, что конфиг Vitest использует те же плагины, что и сборка.
  • Не разрешаются алиасы вида @/ — перенесите resolve.alias в общий конфиг, а не дублируйте в двух файлах.
  • Окружение jsdom не установлено — добавьте пакет в devDependencies, иначе получите ошибку при старте.
  • Моки «протекают» между тестами — включите restoreMocks и очищайте глобальные подмены.
  • Процесс не завершается после прогона — ищите открытые таймеры и соединения; помогает pool: 'forks' и корректное закрытие ресурсов.
  • Снапшоты устарели после рефакторинга — обновляйте их осознанно через vitest -u, а не автоматически в CI.
npx vitest run src/lib/price.test.ts
npx vitest -u
npx vitest --reporter=verbose
npx vitest --help

Локальный запуск одного файла экономит минуты при отладке, а подробный репортер показывает длительность каждого теста. Если тест нестабилен, сначала проверьте общие состояния и реальные таймеры, а не увеличивайте ожидания.

Итог

Vitest даёт быстрый старт без отдельного транспайлера, знакомый API и покрытие из коробки. Минимальный набор — установить пакеты, описать окружение, написать несколько тестов на чистые функции и подключить прогон в CI. Дальше расширяйте покрытие по модулям, которые действительно ломаются, и следите за чистотой моков: именно они чаще всего превращают полезные тесты в хрупкую декорацию.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте