Управление жизненным циклом в тестах

При тестировании компонентов Vue критически важно правильно управлять их жизненным циклом. Жизненный цикл компонента включает создание, монтирование, обновление и уничтожение экземпляра компонента. Некорректная работа с этими этапами может привести к ложноположительным или ложноотрицательным результатам тестов. Vue Test Utils предоставляет набор инструментов для точного контроля жизненного цикла.


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

Основной метод для создания экземпляра компонента в тестах — mount. Он принимает компонент и опции, включая props, slots и global:

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

const wrapper = mount(MyComponent, {
  props: { title: 'Тестовый заголовок' },
  slots: { default: '<p>Содержимое слота</p>' }
})

Ключевой момент: при использовании mount компонент монтируется в виртуальный DOM, что позволяет тестировать его полностью, включая дочерние компоненты и реактивные эффекты. Для более лёгкого тестирования с ограниченной глубиной используется shallowMount, который заменяет дочерние компоненты заглушками.


Вызов хуков жизненного цикла

Vue Test Utils обеспечивает автоматический вызов следующих хуков:

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeUnmount
  • unmounted

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

Пример проверки эффекта mounted:

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

const wrapper = mount(Counter)
expect(wrapper.vm.count).toBe(0) // Значение установлено при mounted

Управление асинхронными обновлениями

Vue использует асинхронный механизм обновления реактивных данных, что требует использования вспомогательных функций для синхронизации тестов с DOM:

  • nextTick() — дожидается завершения всех текущих обновлений DOM.
  • flushPromises() — ожидает выполнения всех обещаний, полезно при работе с API или асинхронными эффектами.

Пример:

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

const wrapper = mount(AsyncComponent)
wrapper.vm.loadData() // Метод, который обновляет данные асинхронно
await nextTick()       // Дожидаемся обновления DOM

expect(wrapper.text()).toContain('Данные загружены')

Размонтирование компонента

Для освобождения ресурсов и предотвращения утечек памяти важно корректно размонтировать компонент после теста. Vue Test Utils предоставляет метод unmount:

wrapper.unmount()

После вызова unmount выполняются хуки beforeUnmount и unmounted, что позволяет проверять корректное очищение таймеров, подписок и сторонних эффектов.


Использование хуков Jest для управления жизненным циклом

В сочетании с Jest полезно использовать встроенные хуки для инициализации и очистки тестовых данных:

  • beforeEach() — выполняется перед каждым тестом.
  • afterEach() — выполняется после каждого теста.

Пример:

let wrapper

beforeEach(() => {
  wrapper = mount(MyComponent)
})

afterEach(() => {
  wrapper.unmount()
})

test('проверка начального состояния', () => {
  expect(wrapper.text()).toContain('Начальное значение')
})

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


Контроль реактивности через setProps и setData

Vue Test Utils позволяет изменять пропсы и данные после монтирования, что активирует обновления компонента:

await wrapper.setProps({ title: 'Новый заголовок' })
await wrapper.setData({ count: 5 })

Каждый вызов автоматически инициирует реактивное обновление и соответствующие хуки жизненного цикла (beforeUpdate, updated), что позволяет проверять реакцию компонента на изменения.


Тестирование жизненного цикла дочерних компонентов

Для компонентов с вложенными детьми важно учитывать глубину монтирования:

  • mount — монтирует полностью, включая всех детей.
  • shallowMount — заменяет дочерние компоненты заглушками, предотвращая вызов их mounted и updated.

Пример:

import { shallowMount } from '@vue/test-utils'
import Parent from '@/components/Parent.vue'

const wrapper = shallowMount(Parent)
expect(wrapper.findComponent({ name: 'Child' }).exists()).toBe(true)

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


Отслеживание вызова хуков с Jest

Для сложных компонентов можно мокировать хуки жизненного цикла с помощью Jest:

jest.spyOn(MyComponent, 'mounted')

mount(MyComponent)
expect(MyComponent.mounted).toHaveBeenCalled()

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


Итоговые рекомендации по управлению жизненным циклом

  • Всегда размонтировать компонент после теста для предотвращения утечек памяти.
  • Использовать nextTick и flushPromises для синхронизации асинхронных обновлений.
  • Выбирать между mount и shallowMount в зависимости от необходимости тестирования дочерних компонентов.
  • Мокировать и отслеживать хуки при необходимости проверки сложного поведения.
  • Комбинировать с хуками Jest (beforeEach и afterEach) для изоляции тестов и контроля состояния компонентов.

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