Жизненный цикл теста: setup, execute, assert

Тест в экосистеме Vue строится вокруг воспроизводимого сценария: подготовка окружения, выполнение действия и проверка результата. Эта структура позволяет писать изолированные, предсказуемые и легко поддерживаемые тесты компонентов с использованием Vue Test Utils и любого раннера (Vitest, Jest).


Setup — подготовка окружения

Этап подготовки формирует исходное состояние теста. Здесь создаётся экземпляр компонента, подменяются зависимости, задаются входные данные и конфигурация окружения.

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

Ключевая операция — mount или shallowMount.

import { mount } from '@vue/test-utils'
import MyComponent from '@/components/MyComponent.vue'

const wrapper = mount(MyComponent)
  • mount — рендерит компонент полностью, включая дочерние компоненты.
  • shallowMount — автоматически подменяет дочерние компоненты заглушками, ускоряя тест и снижая связность.

Выбор зависит от цели теста: поведенческий тест компонента или интеграционная проверка связки.

Передача props

Исходные данные компонента задаются через props.

const wrapper = mount(MyComponent, {
  props: {
    title: 'Test title',
    isActive: true
  }
})

Важно формировать props так, чтобы тест отражал конкретный сценарий, а не универсальный случай.

Глобальная конфигурация

Vue Test Utils предоставляет объект global для настройки окружения:

const wrapper = mount(MyComponent, {
  global: {
    plugins: [store, router],
    stubs: ['RouterLink'],
    mocks: {
      $t: (key) => key
    },
    provide: {
      apiUrl: 'https://example.test'
    }
  }
})
  • plugins — подключение Vuex, Pinia, Router.
  • stubs — подмена компонентов.
  • mocks — имитация глобальных свойств ($t, $axios).
  • provide — внедрение зависимостей через provide/inject.

Подготовка состояния

Если компонент использует хранилище состояния, оно инициализируется заранее:

import { createPinia, setActivePinia } from 'pinia'

setActivePinia(createPinia())

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


Execute — выполнение действия

На этом этапе воспроизводится пользовательское или программное действие, которое изменяет состояние компонента.

Взаимодействие с DOM

Vue Test Utils предоставляет методы-обёртки для событий:

await wrapper.find('button').trigger('click')

await обязателен, так как Vue обновляет DOM асинхронно.

Поддерживаются все стандартные события:

await wrapper.find('input').setValue('hello')
await wrapper.find('form').trigger('submit')

Метод setValue автоматически эмитит input и change.

Вызов публичных методов

Если компонент предоставляет методы через methods или defineExpose, они вызываются напрямую:

wrapper.vm.increment()
await wrapper.vm.$nextTick()

Такой подход полезен для тестирования логики, не связанной напрямую с DOM.

Эмиссия событий от дочерних компонентов

При использовании shallowMount часто требуется имитировать события:

wrapper.findComponent({ name: 'ChildComponent' })
  .vm.$emit('submit', payload)

Это позволяет проверить реакцию родительского компонента без реального рендера дочернего.

Работа с асинхронностью

При наличии async/await, setTimeout, запросов к API требуется дождаться завершения всех микротасок:

await flushPromises()

flushPromises синхронизирует очередь промисов, устраняя нестабильность тестов.


Assert — проверка результата

Этап проверки фиксирует ожидаемый эффект: изменение DOM, состояния, эмиссию событий или вызов побочных эффектов.

Проверка DOM

expect(wrapper.text()).toContain('Success')
expect(wrapper.find('.error').exists()).toBe(false)

Рекомендуется проверять результат, а не конкретную реализацию. Например, наличие текста вместо точной HTML-структуры.

Проверка props и состояния

expect(wrapper.props('isActive')).toBe(true)
expect(wrapper.vm.count).toBe(1)

Проверка внутреннего состояния допустима, если оно является частью публичного поведения компонента.

Проверка эмитированных событий

const events = wrapper.emitted()
expect(events.submit).toHaveLength(1)
expect(events.submit[0]).toEqual([{ value: 42 }])

emitted() возвращает объект, где ключ — имя события, значение — массив аргументов по каждому вызову.

Проверка вызовов моков

Для функций-заглушек используется стандартный механизм моков тест-раннера:

const saveMock = vi.fn()

const wrapper = mount(MyComponent, {
  global: {
    mocks: {
      $save: saveMock
    }
  }
})

await wrapper.find('button').trigger('click')

expect(saveMock).toHaveBeenCalledWith({ id: 1 })

Такая проверка особенно важна для побочных эффектов: логирования, навигации, HTTP-запросов.


Структурирование теста по жизненному циклу

Чёткое разделение на setup, execute, assert повышает читаемость и снижает стоимость поддержки.

it('отправляет форму', async () => {
  // setup
  const wrapper = mount(FormComponent)

  // execute
  await wrapper.find('input').setValue('test')
  await wrapper.find('form').trigger('submit')

  // assert
  expect(wrapper.emitted('submit')).toBeTruthy()
})

Такой формат облегчает поиск ошибок: проблема либо в подготовке, либо в действии, либо в ожиданиях.


Изоляция и повторяемость

Каждый тест должен быть независимым:

  • новое монтирование компонента для каждого кейса;
  • сброс моков между тестами;
  • отсутствие общих изменяемых данных.

Использование beforeEach допустимо для общей подготовки, но логика теста должна оставаться локальной и очевидной.


Связь жизненного цикла теста и жизненного цикла компонента

Монтирование запускает setup, created, mounted. Триггеры событий вызывают обновления (watch, computed, updated). Проверки должны учитывать, на каком этапе находится компонент.

При необходимости проверки промежуточного состояния используется $nextTick или ожидание промисов, чтобы синхронизироваться с реактивной системой Vue.


Жизненный цикл теста setup → execute → assert формирует устойчивый каркас для проверки Vue-компонентов. Он минимизирует неявные зависимости, делает тесты читаемыми и позволяет точно отражать поведение приложения в контролируемом окружении.