Миграция с Vue 2 на Vue 3 тесты

При переходе с Vue 2 на Vue 3 важным аспектом является адаптация тестов компонентов, так как изменилась архитектура фреймворка, API и поведение реактивности. Vue Test Utils (VTU) для Vue 3 предоставляет новые методы и возможности, но также требует пересмотра подходов к тестированию, особенно при работе с слотом, событиями и асинхронной логикой.


Основные изменения во Vue Test Utils для Vue 3

1. Новый API рендеринга В Vue 3 VTU используется функция mount из пакета @vue/test-utils, аналогично Vue 2, но с расширенной поддержкой Composition API и Teleport. Компоненты можно монтировать так же, как и раньше:

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

const wrapper = mount(MyComponent, {
  props: { msg: 'Привет' },
  global: {
    plugins: [],
    components: {},
    directives: {}
  }
})

Ключевой момент: в Vue 3 добавлен объект global, который позволяет подключать плагины, компоненты и директивы в тестовом окружении, аналогично localVue в Vue 2, но с более прозрачным API.


2. Работа с событиями и v-model Vue 3 изменил внутреннюю реализацию v-model. Теперь компонент может иметь несколько v-model, что влияет на тестирование:

const wrapper = mount(MyComponent, {
  props: {
    modelValue: 'тест'
  }
})

await wrapper.setValue('новое значение')
expect(wrapper.emitted('update:modelValue')[0]).toEqual(['новое значение'])

Важно проверять события update:modelValue вместо прямой мутации пропсов, что отличается от подхода Vue 2.


3. Асинхронные обновления DOM Vue 3 использует новую реактивную систему, которая делает обновления DOM асинхронными. Это требует использования nextTick или асинхронных ожиданий при проверке состояния DOM после изменений:

import { nextTick } from 'vue'

await wrapper.setProps({ msg: 'обновлено' })
await nextTick()
expect(wrapper.text()).toContain('обновлено')

Игнорирование асинхронности может привести к ложным отрицательным результатам в тестах.


Работа с слотами и Teleport

1. Слоты Слоты в Vue 3 имеют более строгую проверку типов и могут быть функциональными. Передача слота выполняется через объект slots:

const wrapper = mount(MyComponent, {
  slots: {
    default: '<span>Контент слота</span>'
  }
})

expect(wrapper.html()).toContain('Контент слота')

2. Teleport Компоненты, использующие <teleport>, требуют специального подхода. Vue Test Utils позволяет проверять содержимое, смонтированное в другом месте DOM:

const wrapper = mount(MyComponent, { attachTo: document.body })
expect(document.body.innerHTML).toContain('Телепортированный контент')

Мокинг глобальных зависимостей

В Vue 2 использовался localVue для подключения плагинов, Vuex и роутера. В Vue 3 аналог — объект global:

const wrapper = mount(MyComponent, {
  global: {
    plugins: [store, router],
    mocks: {
      $t: (msg) => msg
    }
  }
})

Это упрощает изоляцию компонента и позволяет тестировать без необходимости создавать полноценный экземпляр приложения.


Тестирование Composition API

Vue 3 активно использует Composition API. Тестирование реактивных переменных и функций требует прямого доступа к возвращаемым значениям setup:

import { ref } from 'vue'

const wrapper = mount({
  template: '<button @click="increment">{{ count }}</button>',
  setup() {
    const count = ref(0)
    const increment = () => count.value++
    return { count, increment }
  }
})

await wrapper.trigger('click')
expect(wrapper.text()).toBe('1')

Важно помнить, что ref и reactive теперь полностью интегрированы с VTU, но их значения проверяются через .text(), .props() или прямой доступ через wrapper.vm.


Рекомендации по миграции тестов

  1. Обновить версии VTU и Jest/Vitest до последних совместимых с Vue 3.
  2. Проверять асинхронность DOM-обновлений с nextTick или flushPromises.
  3. Переписать тесты на слоты и Teleport с учетом новых особенностей.
  4. Использовать глобальный объект global вместо localVue.
  5. Тестировать Composition API через wrapper.vm и реактивные переменные.
  6. Перепроверить обработку событий v-model для компонентов с несколькими моделями.

Пример комплексного теста Vue 3

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

const wrapper = mount(MyForm, {
  global: { plugins: [store] },
  props: { initialValue: '123' },
  slots: { default: '<div>Slot content</div>' }
})

await wrapper.find('input').setValue('456')
await nextTick()
expect(wrapper.emitted('update:modelValue')[0]).toEqual(['456'])
expect(wrapper.html()).toContain('Slot content')

Этот пример показывает интеграцию нескольких аспектов Vue 3: слоты, глобальные плагины, асинхронное обновление DOM и события v-model.


Миграция тестов с Vue 2 на Vue 3 требует внимательного анализа компонентов и их реактивной логики, но новая архитектура VTU делает тестирование более модульным и гибким.