12k
All articles

Что изменилось в Vitest 5

Разберитесь в изменениях Vitest 5: приросте производительности, несовместимых изменениях, настройках по умолчанию, путях отчетов и шагах обновления тестов и CI.

OpenReplay Team
OpenReplay Team
Что изменилось в Vitest 5

Vitest 5.0 вышел 3 сентября 2026 года. Это мажорный релиз с упором на производительность. Он требует Node.js 22.12.0+ и Vite 6.4.0+, включает очистку моков по умолчанию, роняет тесты с асинхронными проверками без await и переносит вывод репортеров в единую директорию .vitest/.

Большинство ошибок после обновления исправляются легко. Сложнее с тестами, которые локально проходят на Vitest 4, а в CI падают с ошибкой, ничего не говорящей о причине.

В этой статье изменения из release notes v5.0.0 разобраны по степени влияния. Сначала о том, что стало быстрее. Затем об изменениях, требующих правки кода, и об изменениях, которые незаметно меняют пути и правила сопоставления. В конце — чек-лист обновления и итоговая рекомендация.

Ключевые выводы

  • Vitest 5.0 требует Node.js 22.12.0 или новее и Vite 6.4.0 или новее.
  • По бенчмаркам команды Vitest, большинство протестированных конфигураций работают на 8–25 % быстрее, а некоторые конфигурации с VM-пулами — до 53 % быстрее. Там, где основное время уходит на подготовку окружения, прирост почти незаметен.
  • clearMocks теперь по умолчанию равен true. Проверка истории вызовов, записанной в setup-файле, хуке beforeAll или предыдущем тесте, теперь видит ноль вызовов.
  • Blob-отчёты, вложения (attachments), а также вывод репортеров JSON, JUnit и HTML по умолчанию сохраняются в .vitest/, поэтому шаги CI, загружающие артефакты, нужно обновить.
  • toThrow('') теперь совпадает с любой выброшенной ошибкой, поэтому для проверки именно пустого сообщения нужен явный шаблон.

Почему Vitest 5 работает быстрее?

Согласно анонсу Vitest 5, большинство конфигураций в собственных бенчмарках команды Vitest работают на 8–25 % быстрее. Больше всего выигрывают VM-пулы: в некоторых конфигурациях — до 53 %. Ускоряется не всё: прогоны, где основное время уходит на создание тестового окружения (например, forks с jsdom и изоляцией), укладываются в пределы 3 % от Vitest 4.1. Цифры получены в репозитории vitest-dev/benchmarks, где команда сгенерировала тестовые приложения разного размера — от небольшого пакета из 5 файлов до монолита из 1280 модулей. В анонсе от VoidZero это округлено до «VM-пулы до 53 % быстрее, прирост ~18 % по всем направлениям, включая Browser Mode».

Согласно release notes, основной прирост дают четыре изменения:

  • Общий Vite-сервер. Inline-проекты теперь используют один Vite-сервер вместо того, чтобы каждый запускал собственный.
  • fsModuleCache. Теперь это опция верхнего уровня. Она сохраняет трансформированные модули на диск, поэтому повторный запуск или другой процесс Vitest может пропустить эту работу.
  • Меньше обменов данными. Уже трансформированные модули теперь попадают в воркер из основного процесса за один проход.
  • Переиспользование в VM-пулах. Пулы vmThreads и vmForks разделяют скомпилированный код между контекстами и заранее загружают граф модулей.

Vitest также поддерживает дисковый кеш компиляции Node, но его нужно включать явно.

Какие изменения в Vitest 5 требуют правки кода?

Шесть изменений в Vitest 5 приводят к падению тестов или ошибке конфигурации уже при первом запуске. Каждое из них описано в руководстве по миграции.

ИзменениеСимптом при первом запускеИсправление
clearMocks: true по умолчаниюПроверки количества вызовов видят 0Вызывайте мок внутри теста с проверкой или задайте clearMocks: false
Асинхронная проверка без awaitТест падаетДобавить await
Поднимаемый (hoisted) вызов vi не на верхнем уровнеВыбрасывается ошибкаПеренести на уровень модуля
Удалён sequentialAPI удалён{ concurrent: false }
Нет поиска конфига в родительских директорияхКонфиг не найденДобавить конфиг в папку пакета
Переписан API бенчмарковСтарый код бенчмарков ломаетсяПерейти на модель с фикстурами

Очистка моков и hoisting

В Vitest 5 clearMocks по умолчанию равен true, поэтому vi.clearAllMocks() выполняется перед каждым тестом. История вызовов, записанная в setup-файле, хуке beforeAll или предыдущем тесте, стирается до того, как следующий тест её проверит. Реализации моков при этом сохраняются.

// Vitest 5.0.x
const track = vi.fn()
beforeAll(() => initAnalytics(track))

it('tracks once on init', () => {
  expect(track).toHaveBeenCalledTimes(1) // now receives 0 calls
})

Исправление — выполнять вызов внутри того же теста, который его проверяет. Параметр clearMocks: false в секции test конфига возвращает прежнее поведение на время аудита тестов.

Вызов vi.mock или любого другого поднимаемого вызова vi где-либо, кроме верхнего уровня файла, теперь выбрасывает ошибку. Vitest в любом случае поднимает такие вызовы в начало модуля, так что код внутри блока describe никогда не выполнялся там, где был написан.

// Vitest 5.0.x: throws
describe('UserCard', () => {
  const fetchUser = vi.fn()
  vi.mock('./api', () => ({ fetchUser }))
})

// Vitest 5.0.x: works
const { fetchUser } = vi.hoisted(() => ({ fetchUser: vi.fn() }))
vi.mock('./api', () => ({ fetchUser }))

describe('UserCard', () => {
  it('renders the user', async () => {
    fetchUser.mockResolvedValue({ name: 'Ada' })
    // mount and assert
  })
})

Если в ваших Vue-тестах мокается слой API, тот же паттерн с верхним уровнем применим и к мокированию API-вызовов в Vue-тестах с Vitest.

Проверки без await

Тест, в котором асинхронная проверка осталась без await, теперь падает. Пропущенный await перед expect(...).resolves или .rejects делает тест красным.

// Vitest 5.0.x
test('loads config', async () => {
  expect(loadConfig()).resolves.toEqual({ ok: true }) // fails
  await expect(loadConfig()).resolves.toEqual({ ok: true }) // passes
})

Эта команда grep выводит список кандидатов. Каждое совпадение всё равно нужно проверить на наличие await в начале:

grep -rnE "expect\(.*\)\.(resolves|rejects)" src/ --include='*.test.*' --include='*.spec.*'

Конкурентность, поиск конфига и бенчмарки

Опции sequential для тестов и наборов тестов удалены. Чтобы отключить конкурентное выполнение, используйте test('example', { concurrent: false }, ...) или describe('suite', { concurrent: false }, ...).

Vitest 5 больше не ищет файл конфигурации в директориях выше текущей. Если вы запускаете vitest из подпапки пакета, в этой папке должен быть собственный конфиг.

API бенчмарков переписан. Теперь bench не импортируется в начале файла — вместо этого он берётся из контекста теста внутри обычного вызова test() в файле бенчмарка.

В release notes перечислены и другие ломающие изменения — проверьте, касаются ли они вас:

  • expect.poll теперь падает по истечении тайм-аута.
  • Удалены устаревшие точки входа (entry points).
  • @vitest/runner объявлен устаревшим, а vitest больше не зависит от @vitest/expect, поскольку код проверок теперь поставляется внутри самого vitest.
  • Провайдер @vitest/browser-webdriverio переехал в организацию vitest-community и теперь поддерживается сообществом.
  • workerId теперь нумеруется с 1.

toThrow('') теперь совпадает с любой выброшенной ошибкой. Если нужно проверить именно пустое сообщение, передайте регулярное выражение, например /^$/.

Что незаметно меняется в Vitest 5?

Шесть изменений в Vitest 5 не выбрасывают ошибок. Вместо этого под вашей текущей конфигурацией меняется путь, фильтр или результат сопоставления.

  • Пути вывода. Blob-отчёты и --merge-reports по умолчанию используют .vitest/blob/. Вложения переехали из .vitest-attachements/ в .vitest/attachments/. Файлы репортеров JSON, JUnit и HTML тоже по умолчанию сохраняются в .vitest.
  • Фильтры -t. Разделителем в фильтрах по имени теста теперь служит >. Проверьте CI-скрипты, которые фильтруют тесты по пути набора.
  • Локаторы в браузере. В Browser Mode locators.exact теперь включён по умолчанию.
  • Сопоставление текста. toHaveTextContent теперь работает строго. Новая альтернатива — toMatchTextContent.
  • Glob-шаблоны покрытия. Шаблоны include и exclude теперь сопоставляются с путём каждого файла относительно корня проекта, а шаблон без wildcard-символов считается целой папкой. Набор файлов, учитываемых в покрытии, может измениться, поэтому после первого запуска проверьте пороговые значения (thresholds).
  • Inline-проекты. Inline-проекты теперь наследуют корневой конфиг так, как будто задано extends: true.

Типичный шаг загрузки артефактов меняется так:

-          path: .vitest-attachements/
+          path: .vitest/attachments/
+          # sharded runs: upload .vitest/blob/ for --merge-reports

Что нового в Vitest 5 стоит знать

vi.when позволяет задать спаю разный результат для каждого набора аргументов. calledWith принимает асимметричные матчеры, а вызовы с аргументами, которые ни с чем не совпали, передаются в исходную реализацию.

// Vitest 5.0.x
vi.when(getUser).calledWith(1).thenResolve({ id: 1, name: 'Ada' })

В Browser Mode параметр test.browser.traceView: true включает режим просмотра трассировки (trace view). Каждое взаимодействие, проверка и вызов page.mark сохраняются как снимок DOM, так что тест можно пошагово воспроизвести в UI.

Теперь поддерживаются вложенные проекты, что помогает группировать связанные проекты в монорепозиториях.

Чек-лист обновления до Vitest 5

  1. Переведите CI и локальные окружения на Node.js 22.12.0+ и Vite 6.4.0+.
  2. Запустите приведённую выше команду grep и добавьте await везде, где он отсутствует.
  3. Перенесите все вызовы vi.mock и vi.hoisted на верхний уровень файла.
  4. Замените sequential на { concurrent: false } и добавьте конфиги в папки пакетов, которые полагались на родительский конфиг.
  5. Запустите тесты. Если падают проверки количества вызовов, исправьте их или временно задайте clearMocks: false.
  6. Обновите пути к артефактам в CI на .vitest/ и проверьте фильтры -t и пороговые значения покрытия.

Обновляться до Vitest 5 сейчас или подождать?

Обновляйтесь до Vitest 5 в текущем спринте, если ваш CI уже работает на Node.js 22.12.0+ и Vite 6.4.0+. Большинство необходимых правок механические.

Исключение — наборы тестов, которые проверяют историю вызовов моков, переносимую между тестами: из setup-файлов, хуков beforeAll или когда один тест полагается на вызовы из другого. Такие падения никак не указывают на причину, поэтому сначала проведите аудит этих проверок, а затем обновляйтесь. В наборах тестов компонентов также стоит перезапустить проверки текста и локаторов; паттерны из статьи о тестировании компонентов Svelte 5 с Vitest показывают, где они обычно встречаются.

Vitest 5 быстрее, а большая часть того, что он ломает, — это тестовый код, который и так был некорректным. Начните в отдельной ветке с grep и переноса vi.mock, проверьте пути к артефактам в CI, а остальное подскажет первый прогон в CI.

Часто задаваемые вопросы

Включает ли Vitest 5 по умолчанию также mockReset или restoreMocks?

Нет. Согласно руководству по миграции на Vitest 5, значение по умолчанию изменено только для clearMocks. clearMocks вызывает vi.clearAllMocks() перед каждым тестом и сбрасывает mock.calls, mock.instances, mock.contexts и mock.results, но сохраняет реализации. mockReset идёт дальше: он очищает историю и возвращает каждую реализацию к исходной, так что мок, созданный через vi.fn(impl), снова использует impl. restoreMocks восстанавливает исходные реализации спаев, созданных через vi.spyOn.

Почему после обновления до Vitest 5 мой фильтр -t находит меньше тестов?

В Vitest 5 testNamePattern (флаг -t) сопоставляется с полным именем теста, которое строится через ' > ' между названием каждого набора и названием теста. Это тот же текст, что вы видите в выводе репортера. Vitest 4, как и Jest, использовал между частями одиночный пробел. Шаблон ломается, только если он переходит из одной части имени в другую. Чтобы это исправить, сопоставляйте только одну часть, например -t adds, или поставьте wildcard между частями, например -t 'math.*adds'.

Почему Vitest 5 не может найти vite после обновления через Yarn?

В Vitest 5 vite перестал быть прямой зависимостью и стал обязательной peer-зависимостью, поэтому Vitest работает с той версией Vite, которая установлена в вашем проекте. npm, pnpm, Bun и Deno добавляют peer-зависимости автоматически, а Yarn оставляет этот шаг вам. Добавьте vite версии 6.4.0 или новее в package.json и переустановите зависимости — после этого Vitest снова сможет его найти.

Как объединить отчёты шардированных тестов в Vitest 5?

Запустите каждый шард с blob-репортером, например vitest run --reporter=blob --shard=1/3 на первой машине. По умолчанию каждый шард записывает результаты в .vitest/blob/, а флаг --outputFile.blob позволяет изменить это расположение. Скопируйте директорию с каждой машины в одну финальную задачу и запустите vitest --merge-reports. Если ваши тесты сохраняют вложения в виде файлов, передайте в задачу объединения и папку с вложениями.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.