Тестирование setup композиционного API

Vue Test Utils (VTU) предоставляет мощный набор инструментов для тестирования компонентов Vue 3. При работе с Composition API и функцией setup подход к тестированию отличается от привычного Options API. Основной принцип заключается в том, что логика компонента теперь инкапсулируется в реактивных переменных, ref и reactive объектах, а также в функциях, возвращаемых из setup. Тесты должны учитывать эти особенности для корректного взаимодействия с компонентом.


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

Для тестирования компонентов с setup используется функция mount из VTU. Она создает виртуальное представление компонента и предоставляет доступ к его реактивному состоянию и DOM-элементам.

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

test('инкремент счетчика', async () => {
  const wrapper = mount(Counter)
  const button = wrapper.find('button')
  
  await button.trigger('click')
  
  expect(wrapper.text()).toContain('1')
})

Ключевые моменты:

  • mount полностью рендерит компонент, включая дочерние компоненты.
  • shallowMount используется для изоляции теста от вложенных компонентов.
  • В тестах для setup важно использовать async/await при взаимодействии с событиями, потому что реактивность обновляется асинхронно.

Тестирование реактивных переменных

В setup реактивные данные создаются через ref и reactive. Для проверки их значений можно использовать доступ к wrapper.vm, где vm — это экземпляр компонента с реактивными переменными.

import { ref } from 'vue'

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

expect(wrapper.vm.count).toBe(0)
await wrapper.vm.increment()
expect(wrapper.vm.count).toBe(1)

Особенности:

  • ref хранит значение в свойстве .value.
  • Прямой доступ к vm позволяет проверять и модифицировать состояние без взаимодействия с DOM.
  • reactive объекты тестируются аналогично, через свойства объекта.

Работа с вычисляемыми свойствами и наблюдателями

computed и watch играют ключевую роль в логике компонентов на Composition API. Проверка вычисляемых свойств осуществляется через vm, а реакции watch проверяются косвенно — через изменение реактивных данных и последующую проверку DOM или состояния.

import { reactive, computed } from 'vue'

const wrapper = mount({
  setup() {
    const state = reactive({ count: 0 })
    const doubled = computed(() => state.count * 2)
    return { state, doubled }
  },
  template: `<div>{{ doubled }}</div>`
})

expect(wrapper.vm.doubled).toBe(0)
wrapper.vm.state.count = 5
expect(wrapper.vm.doubled).toBe(10)

Важные моменты:

  • Изменения реактивных данных автоматически обновляют вычисляемые свойства.
  • Для тестирования watch можно изменять исходные данные и проверять побочные эффекты (например, вызов функций или изменение DOM).

Тестирование методов и функций setup

Функции, возвращаемые из setup, можно тестировать напрямую через wrapper.vm. Это позволяет изолированно проверять бизнес-логику без необходимости триггерить DOM-события.

const wrapper = mount({
  setup() {
    const count = ref(0)
    const increment = () => count.value++
    return { count, increment }
  },
  template: `{{ count }}`
})

wrapper.vm.increment()
expect(wrapper.vm.count).toBe(1)

Рекомендации:

  • Использовать прямой вызов функций для unit-тестов.
  • DOM-интеракции оставлять для интеграционных тестов.

Тестирование асинхронной логики

Асинхронные операции (async/await, setTimeout, API-запросы) требуют ожидания завершения реактивных обновлений. Для этого VTU предоставляетnextTick` и встроенную поддержку асинхронных операций.

import { nextTick, ref } from 'vue'

const wrapper = mount({
  setup() {
    const count = ref(0)
    const incrementAsync = async () => {
      await new Promise(resolve => setTimeout(resolve, 100))
      count.value++
    }
    return { count, incrementAsync }
  },
  template: `{{ count }}`
})

await wrapper.vm.incrementAsync()
await nextTick()
expect(wrapper.vm.count).toBe(1)

Особенности:

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

Мокирование зависимостей и provide/inject

setup активно использует контекст компонента через provide и inject. В тестах VTU позволяет передавать зависимости через опции global.provide.

const wrapper = mount(MyComponent, {
  global: {
    provide: {
      api: { fetchData: () => Promise.resolve('data') }
    }
  }
})

Принципы:

  • Любые внешние зависимости следует мокировать.
  • Это позволяет тестировать компонент в изоляции без реального API или хранилища.

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

setup возвращает функции и реактивные данные, но эмиты событий работают аналогично Options API через wrapper.emitted().

const wrapper = mount({
  setup(_, { emit }) {
    const send = () => emit('update', 42)
    return { send }
  },
  template: `<button @click="send">Send</button>`
})

await wrapper.find('button').trigger('click')
expect(wrapper.emitted().update[0]).toEqual([42])

Особенности:

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

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

При тестировании компонентов с Composition API важно проверять рендеринг слотов. VTU поддерживает передачу слотов через опцию slots при монтировании.

const wrapper = mount(MyComponent, {
  slots: {
    default: '<span class="slot-content">Hello</span>'
  }
})

expect(wrapper.find('.slot-content').exists()).toBe(true)

Принцип:

  • Слоты должны рендериться корректно, независимо от того, используется ли setup.
  • Проверка слотов позволяет убедиться, что компонент корректно взаимодействует с переданным контентом.

Итоги по тестированию setup

  • Основной доступ к данным и методам происходит через wrapper.vm.
  • Реактивность требует использования await и nextTick при изменении данных.
  • Асинхронные операции и вычисляемые свойства требуют проверки через состояние компонента и DOM.
  • Эмиты, слоты и provide/inject тестируются через глобальные опции и методы VTU.
  • Прямой вызов функций setup позволяет изолировать бизнес-логику от DOM.

Тестирование компонентов с Composition API при правильном подходе обеспечивает высокую уверенность в стабильности и корректности логики Vue-приложений.