Jest или Vitest: что выбрать для тестов на JS в 2024
Jest тащит на себе почти любой legacy-проект, Vitest выигрывает по скорости старта на Vite-стеке. Разбираем, чем вы платите за каждый и когда миграция окупается.

Задача: тесты стали медленными и мешают работать
Проект на React, 1200 тестов, jest прогоняет их за 90 секунд на CI и секунд за 30 в watch-режиме на локали. Разработчики перестали запускать тесты перед коммитом — слишком долго. Возникает соблазн переехать на Vitest: он обещает старт за миллисекунды и ESM из коробки. Вопрос в том, во что обойдётся переезд и стоит ли он экономии времени. Ниже — сравнение двух раннеров по тем параметрам, которые реально влияют на решение, а не по маркетинговым цифрам с лендинга.
Речь про Jest 29.x и Vitest 1.x / 2.x. Оба — тест-раннеры с ассертами, моками и покрытием в комплекте. Синтаксис тестов почти идентичен: describe, it, expect. Различия начинаются в том, как они запускают код и как встраиваются в сборку.
Ключевое отличие: как раннер понимает ваш код
Jest долгое время работал через CommonJS и трансформацию файлов по требованию (обычно через babel-jest или ts-jest). ESM-поддержка в Jest есть, но остаётся экспериментальной и требует флага --experimental-vm-modules у Node — это боль, если проект целиком на нативных ES-модулях.
Vitest использует Vite как движок трансформации. Если ваша сборка уже на Vite, тесты гоняются через тот же конфиг, те же алиасы, те же плагины. ESM — родной режим. Отсюда и скорость холодного старта: Vite не перекомпилирует весь граф зависимостей заранее, а трансформирует модули по мере обращения.
Vitest быстр не потому, что «написан лучше», а потому что переиспользует инфраструктуру Vite. Если у вас нет Vite, половина этого преимущества испаряется.
Где Jest всё ещё сильнее
- Экосистема. Тысячи гайдов, готовые пресеты (
ts-jest,jest-preset-angular), интеграции с любым инструментом. У Vitest экосистема моложе, хотя догоняет быстро. - Стабильность API. Jest почти не ломает совместимость между минорами. Vitest в 1.x → 2.x менял поведение некоторых моков и опций конфига.
- Изоляция по умолчанию. Jest запускает каждый тестовый файл в отдельном окружении. Vitest тоже умеет это, но по умолчанию в некоторых режимах шарит контекст ради скорости — и это ловушка (см. ниже).
Сравнение по параметрам
| Параметр | Jest 29.x | Vitest 1.x/2.x |
|---|---|---|
| Холодный старт | Медленнее, компилирует заранее | Быстрый на Vite-проектах |
| ESM | Экспериментально, с флагом Node | Нативно |
| TypeScript | Через ts-jest или babel | Через esbuild из коробки (без проверки типов) |
| Совместимость API тестов | — | Совместим с Jest на 90%+ |
| Watch-режим | Работает, перезапускает затронутое | Быстрее за счёт HMR-подхода |
| Браузерный режим | Через jsdom/happy-dom | jsdom/happy-dom + экспериментальный browser mode |
| Зрелость экосистемы | Высокая | Средняя, растёт |
Синтаксис почти совпадает — но моки разные
Тело теста переносится один в один. Отличается только импорт API и способ мокать модули. В Jest глобалы (describe, expect) доступны без импорта, в Vitest их либо включают в конфиге через globals: true, либо импортируют явно.
Vitest, явные импорты:
// example.test.ts — Vitest 2.x
import { describe, it, expect, vi } from 'vitest'
import { formatPrice } from './price'
describe('formatPrice', () => {
it('добавляет символ рубля и разделитель тысяч', () => {
expect(formatPrice(1500)).toBe('1 500 ₽')
})
it('округляет копейки вниз', () => {
expect(formatPrice(99.99)).toBe('99 ₽')
})
})Мок модуля. В Jest — jest.mock, в Vitest — vi.mock. Разница не только в имени: у Vitest фабрика мока по умолчанию не поднимается автоматически так же, как в Jest, и порядок объявлений важнее.
// Vitest 2.x — мок модуля с fetch
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { loadUser } from './user'
// vi.mock поднимается наверх файла, как и jest.mock
vi.mock('./api', () => ({
fetchUser: vi.fn(() => Promise.resolve({ id: 1, name: 'Аня' })),
}))
describe('loadUser', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('возвращает имя пользователя', async () => {
const user = await loadUser(1)
expect(user.name).toBe('Аня')
})
})При миграции с Jest самый частый ручной труд — заменить jest. на vi. и разобраться с таймерами. vi.useFakeTimers() есть, но набор методов и поведение по умолчанию отличаются от jest.useFakeTimers(). Проверяйте каждый тест с таймерами вручную.
Когда что выбирать
- Стек уже на Vite (Vue, современный React/SolidStart). Берите Vitest. Один конфиг на сборку и тесты, нет дубля алиасов и трансформеров, быстрый watch. Это его домашнее поле.
- Стек на Webpack / Next.js / Create React App / Metro (React Native). Оставайтесь на Jest. Next.js официально документирует Jest-настройку, React Native ориентирован на Jest. Ставить Vite ради тестов — обмен шила на мыло.
- Большой legacy с тысячами тестов и кастомными трансформерами. Считайте стоимость миграции. Экономия 40 секунд на CI не окупит недели переписывания моков и таймеров.
- Новый проект без наследия. Смотрите на сборщик. На Vite — Vitest, иначе Jest.
Что ломается на практике
Проблемы, на которые натыкаются при переезде и при работе с Vitest:
- Общий контекст между тестами. Vitest ради скорости может выполнять несколько тестовых файлов в одном воркере/потоке. Глобальное состояние (моки модулей, синглтоны,
process.env) протекает между файлами. Симптом: тест проходит поодиночке и падает в общем прогоне. Лечение —isolate: trueв конфиге пула или явный сброс состояния вafterEach. - TypeScript без проверки типов. Vitest транспилирует TS через esbuild и типы не проверяет — как и
babel-jest. Ошибка типа не завалит тест. Проверку типов держите отдельным шагомtsc --noEmit. - Отличия happy-dom и jsdom. И Jest, и Vitest поддерживают оба DOM-эмулятора. happy-dom быстрее, но реализует не все API. Тест, зелёный на jsdom, может упасть на happy-dom из-за отсутствующего метода. Указывайте окружение явно и не меняйте его «за компанию».
- Снапшоты. Формат сериализации у Jest и Vitest близок, но не идентичен. После миграции снапшоты придётся перегенерировать (
vitest -u), а значит — заново отревьюить, что именно записалось.
Что делать дальше
Если решаете мигрировать — не переключайте весь проект разом. Настройте Vitest параллельно Jest на одной папке тестов, прогоните оба раннера на CI и сравните: время, зелёные/красные, различия в снапшотах. Дальше переносите пакетами и на каждом шаге смотрите на тесты с таймерами и моками — именно там прячутся расхождения.
Если остаётесь на Jest, но упёрлись в скорость — до миграции попробуйте дешёвые рычаги: увеличьте maxWorkers, включите кеш трансформации, разбейте медленные интеграционные тесты в отдельный прогон от юнитов. Часто этого хватает, чтобы watch снова стал терпимым.
Мерить стоит две вещи: время холодного прогона на CI и время реакции watch-режима на изменение одного файла. Второе разработчики чувствуют каждый день, и именно оно решает, запускают ли тесты вообще.
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: Vitest — официальная документация, Jest — официальная документация
Частые вопросы
У меня Vitest 1.x, а примеры для 2.x — сработает?
Можно ли перенести Jest-тесты в Vitest без переписывания?
Vitest реально быстрее Jest или это маркетинг?
Тест проходит по одному, но падает в общем прогоне — почему?
Проверяет ли Vitest типы TypeScript при запуске тестов?
Мигрировать ли большой проект на Webpack/Next.js ради скорости?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.