Тестирование с Vue Test Utils

При работе с библиотекой Vuetify тестирование компонентов имеет свои особенности, так как большинство элементов завязано на внутренние стили, слоты и реактивное управление состоянием через v-model. Vue Test Utils предоставляет мощный инструмент для создания юнит-тестов и интеграционных тестов Vue-компонентов, включая компоненты Vuetify.

Настройка окружения для тестирования

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

import { createLocalVue, mount } from '@vue/test-utils'
import Vuetify from 'vuetify'
import Vue from 'vue'

Vue.use(Vuetify)

const localVue = createLocalVue()

function mountVuetifyComponent(component, options = {}) {
  const vuetify = new Vuetify()
  return mount(component, {
    localVue,
    vuetify,
    ...options,
  })
}

Важным моментом является использование Vue.use(Vuetify) и передача экземпляра vuetify при монтировании. Без этого многие компоненты (например, v-btn, v-dialog) будут рендериться некорректно.

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

Для проверки того, что компонент корректно отображается, используют метод find или findComponent:

import MyButton from '@/components/MyButton.vue'

test('рендеринг кнопки с текстом', () => {
  const wrapper = mountVuetifyComponent(MyButton, {
    propsData: { label: 'Нажми меня' }
  })

  const button = wrapper.find('button')
  expect(button.exists()).toBe(true)
  expect(button.text()).toBe('Нажми меня')
})

В случае компонентов Vuetify часто лучше использовать findComponent с указанием v-btn:

const button = wrapper.findComponent({ name: 'VBtn' })
expect(button.text()).toBe('Нажми меня')

Тестирование событий и пользовательских действий

Vuetify-компоненты активно используют события. Vue Test Utils позволяет проверять их с помощью метода trigger и emitted:

const wrapper = mountVuetifyComponent(MyButton)
const button = wrapper.findComponent({ name: 'VBtn' })

button.trigger('click')
expect(wrapper.emitted('click')).toBeTruthy()

Для проверки v-model используют синтаксис привязки:

import MyInput from '@/components/MyInput.vue'

test('изменение значения через v-model', async () => {
  const wrapper = mountVuetifyComponent(MyInput, {
    propsData: { value: 'initial' }
  })

  const input = wrapper.findComponent({ name: 'VTextField' })
  input.setValue('новое значение')

  await wrapper.vm.$nextTick()
  expect(wrapper.emitted('input')[0]).toEqual(['новое значение'])
})

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

Многие компоненты Vuetify используют слоты для кастомизации содержимого. Проверка слотов выполняется через передачу контента при монтировании:

import MyCard from '@/components/MyCard.vue'

test('рендеринг кастомного слота', () => {
  const wrapper = mountVuetifyComponent(MyCard, {
    slots: {
      default: '<span class="custom-slot">Контент</span>'
    }
  })

  expect(wrapper.find('.custom-slot').exists()).toBe(true)
  expect(wrapper.text()).toContain('Контент')
})

Асинхронные компоненты и динамические свойства

Многие Vuetify-компоненты, такие как v-dialog, v-menu, управляются через свойства value или v-model. Тестирование требует использования nextTick:

import MyDialog from '@/components/MyDialog.vue'

test('открытие и закрытие диалога', async () => {
  const wrapper = mountVuetifyComponent(MyDialog)
  wrapper.setProps({ value: true })
  await wrapper.vm.$nextTick()

  expect(wrapper.findComponent({ name: 'VDialog' }).vm.value).toBe(true)

  wrapper.setProps({ value: false })
  await wrapper.vm.$nextTick()
  expect(wrapper.findComponent({ name: 'VDialog' }).vm.value).toBe(false)
})

Моки и заглушки Vuetify

При тестировании можно использовать stubs для снижения зависимости от стилей и поведения:

const wrapper = mount(MyButton, {
  stubs: ['v-icon', 'v-btn'],
})

Это ускоряет тесты и позволяет сфокусироваться на логике компонента, а не на визуальном рендеринге Vuetify.

Проверка реактивности и слежение за состоянием

Vuetify часто использует внутренние свойства для управления состоянием, например v-model и active-class. Проверка изменений состояния выполняется через реактивные свойства:

import MySwitch from '@/components/MySwitch.vue'

test('изменение состояния переключателя', async () => {
  const wrapper = mountVuetifyComponent(MySwitch, {
    propsData: { value: false }
  })

  const switchInput = wrapper.findComponent({ name: 'VSwitch' })
  switchInput.vm.$emit('change', true)
  await wrapper.vm.$nextTick()

  expect(wrapper.emitted('input')[0]).toEqual([true])
})

Интеграция с Jest и покрытие тестами

Vuetify-компоненты часто тестируют в связке с Jest, так как он поддерживает асинхронные тесты и мокирование модулей. Для покрытия тестами рекомендуется:

  • Разделять тесты на рендеринг, события и реактивное состояние.
  • Использовать findComponent вместо find для Vuetify-компонентов.
  • Применять nextTick после изменения props или событий.
  • Создавать отдельный хелпер для монтирования с Vuetify (mountVuetifyComponent).

Эти практики позволяют писать стабильные и предсказуемые юнит-тесты для компонентов Vuetify, избегая ошибок из-за особенностей стилизации и внутренней логики компонентов.