Vue

Тестирование: Vitest и Playwright

Форма оформления заказа работала полгода, пока рефакторинг компонента кнопки не перестал эмитить событие submit. На проде это всплыло жалобой: пользователи нажимают Оплатить, ничего не происходит. Юнит-тест на тридцать строк, который монтирует кнопку, кликает и проверяет emitted, поймал бы регресс за миллисекунды до деплоя. А end-to-end тест на Playwright прошёл бы весь путь от корзины до подтверждения в настоящем браузере. Этот урок про две границы тестирования: Vitest с Vue Test Utils для компонентов и композаблов, и Playwright для сквозных сценариев.

  • Vitest: дефолтный тест-раннер для проектов на Vite, использует тот же конвейер трансформации
  • Vue Test Utils: mount и shallowMount монтируют компонент в тесте и дают доступ к его выводу
  • findComponent и emitted: проверка взаимодействия с дочерними компонентами и испускаемых событий
  • Тестирование композаблов: чистая логика из useX проверяется без рендеринга компонента
  • Playwright: end-to-end тесты прогоняют реальный сценарий в Chromium, Firefox и WebKit

Предварительные знания

  • Понимание компонентов SFC, их пропсов и событий
  • Базовое знание композаблов как функций с реактивным состоянием
  • Идея разницы между юнит-тестом и сквозным тестом

Vitest и монтирование компонента

Vitest это тест-раннер, построенный на том же конвейере, что и Vite. Он переиспользует резолвинг модулей, трансформацию SFC и алиасы из конфига приложения, поэтому тесты видят код ровно так же, как видит его сборка. API совместим с Jest (describe, it, expect), но запуск быстрее за счёт esbuild и горячего перезапуска только затронутых тестов.

Чтобы протестировать компонент, его монтируют через Vue Test Utils. Функция mount создаёт полноценный экземпляр и рендерит всё дерево, включая дочерние компоненты. Возвращается wrapper: обёртка с методами для поиска элементов, имитации событий и проверки вывода. Альтернатива это shallowMount, который заглушает дочерние компоненты стабами и рендерит только тестируемый, изолируя его от детей.

ФункцияЧто делаетКогда выбирать
mountРендерит компонент со всеми детьмиПроверка интеграции с дочерними компонентами
shallowMountЗаглушает детей стабамиИзоляция логики только тестируемого компонента

Имитация событий и обновление DOM в Vue асинхронны: после trigger или изменения состояния DOM обновляется на следующем тике. Поэтому методы вроде trigger возвращают промис, и перед проверкой результата его ожидают через await. Без await тест проверит DOM до обновления и упадёт без видимой причины.

Чем mount отличается от shallowMount в Vue Test Utils?

findComponent и проверка emitted

Контракт компонента это его пропсы на входе и события на выходе. Чтобы проверить выход, Vue Test Utils записывает все испускаемые события в wrapper.emitted(). Метод возвращает объект, где ключ это имя события, а значение это массив вызовов, каждый со своими аргументами. Так тест убеждается, что компонент эмитит нужное событие с правильной нагрузкой именно тогда, когда должен.

Когда нужно добраться до дочернего компонента (проверить переданный ему проп или испускаемое им событие), используется findComponent. В отличие от find, который ищет по CSS-селектору, findComponent находит экземпляр компонента по его определению. Это даёт типизированный доступ к props и emitted конкретного ребёнка.

emitted без аргументов возвращает все события компонента, а с именем только указанное. Возвращаемое значение может быть undefined, если событие ни разу не испускалось, поэтому к результату обращаются через optional chaining, а не предполагают, что массив точно есть.

Тест должен убедиться, что компонент эмитит событие submit с введённым значением. Какой инструмент Vue Test Utils это проверяет?

Тестирование композаблов и Playwright

Композабл это обычная функция, которая возвращает реактивное состояние и методы. Если он не зависит от жизненного цикла компонента, его тестируют простым вызовом: дёргают функцию, меняют состояние, проверяют результат. Никакого монтирования не нужно, потому что вся логика живёт в самой функции, а не в шаблоне.

Если композабл использует хуки жизненного цикла (onMounted, onUnmounted) или provide и inject, прямой вызов не сработает, ведь эти API требуют активного экземпляра компонента. Тогда композабл оборачивают в тестовый компонент и монтируют его через Vue Test Utils, проверяя поведение уже внутри контекста.

Юнит-тесты проверяют отдельные единицы в изоляции. Но они не гарантируют, что приложение целиком работает: роутинг, реальные запросы, рендеринг в настоящем браузере. Это задача end-to-end тестов на Playwright. Он запускает приложение в реальном Chromium, Firefox или WebKit, кликает как пользователь, ждёт навигации и проверяет видимое на странице. Playwright медленнее юнит-тестов, поэтому им покрывают критические сценарии целиком, а не каждую кнопку.

УровеньИнструментЧто проверяет
ЮнитVitest, прямой вызовЛогику композабла или функции
КомпонентVitest, Vue Test UtilsРендер, пропсы, события компонента
СквознойPlaywrightПолный сценарий в реальном браузере

Соблазн покрыть всё сквозными тестами заканчивается медленным и хрупким набором: каждый прогон гоняет браузер, а любая мелкая правка верстки ломает селекторы. Базу покрытия дают быстрые юнит и компонентные тесты, а Playwright оставляют для немногих ключевых пользовательских путей.

Композабл useCounter возвращает count, increment и reset и не использует хуки жизненного цикла. Как его тестировать?

Связь с другими темами

Урок опирается на компоненты и типизацию и связан со сборкой:

  • Компоненты SFC — Тестируется именно компонент: его рендер, пропсы и события
  • TypeScript в Vue — Типизированный контракт пропсов и эмитов задаёт, что именно проверять в тесте
  • Сборка: Vite 8 — Vitest переиспользует трансформацию Vite, поэтому конфиг общий с приложением

Итог

  • Vitest это тест-раннер на конвейере Vite: тот же резолвинг и трансформация, что у приложения, плюс быстрый watch-режим
  • Vue Test Utils монтирует компонент в тесте: mount рендерит и дочерние компоненты, shallowMount заглушает их стабами
  • wrapper.find и wrapper.findComponent находят DOM-элемент или дочерний компонент, а trigger имитирует событие
  • wrapper.emitted возвращает зафиксированные испускаемые события с их аргументами, что проверяет контракт компонента
  • Композабл это обычная функция с реактивным состоянием, поэтому он тестируется вызовом напрямую без монтирования компонента
  • Playwright прогоняет сквозной сценарий в реальном браузере, проверяя поведение всего приложения, а не отдельной единицы

Связанные уроки

  • vue-40-typescript-vue — Типизированный контракт пропсов и эмитов делает тесты точными и проверяемыми
  • vue-43-build-tooling — Vitest использует тот же конвейер трансформации Vite, что и сборка приложения
Тестирование: Vitest и Playwright

0

1

Войти