Unit тестирование компонентов

Unit-тестирование в контексте Vuetify сосредоточено на проверке отдельных компонентов Vue на корректность их поведения, реактивности и взаимодействия с пользовательским интерфейсом. Важно понимать, что Vuetify предоставляет множество готовых UI-компонентов, которые часто имеют сложную структуру с внутренними состояниями и слотовой системой. Цель unit-тестов — убедиться, что компонент работает как ожидается в изоляции, без необходимости полного рендера приложения.


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

Для тестирования компонентов Vuetify обычно используют Vue Test Utils совместно с Jest. Основные шаги настройки:

  1. Установка зависимостей:
npm install @vue/test-utils jest vue-jest babel-jest vuetify --save-dev
  1. Конфигурация Jest в package.json или в отдельном jest.config.js:
{
  "moduleFileExtensions": ["js", "vue"],
  "transform": {
    "^.+\\.vue$": "vue-jest",
    "^.+\\.js$": "babel-jest"
  }
}
  1. Инициализация Vuetify в тестах:
import Vue from 'vue';
import Vuetify from 'vuetify';
import { mount } from '@vue/test-utils';
import MyComponent from '@/components/MyComponent.vue';

Vue.use(Vuetify);

let vuetify;

beforeEach(() => {
  vuetify = new Vuetify();
});

test('рендерит компонент', () => {
  const wrapper = mount(MyComponent, {
    vuetify
  });
  expect(wrapper.exists()).toBe(true);
});

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


Тестирование рендера и слотов

Vuetify активно использует слоты и scoped slots. Unit-тестирование должно проверять, как компонент рендерит эти слоты.

const wrapper = mount(MyComponent, {
  vuetify,
  slots: {
    default: '<span class="slot-content">Тестовый слот</span>'
  }
});

expect(wrapper.find('.slot-content').text()).toBe('Тестовый слот');

Для scoped slots можно использовать функцию, возвращающую контент:

const wrapper = mount(MyComponent, {
  vuetify,
  scopedSlots: {
    item: '<template #item="{ item }">{{ item.name }}</template>'
  }
});

Это позволяет тестировать компоненты вроде v-data-table или v-select, где контент динамически зависит от переданных данных.


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

Vuetify компоненты часто эмитируют события (@click, @input, @change). Тестирование включает проверку этих событий:

const wrapper = mount(MyButtonComponent, { vuetify });
wrapper.find('button').trigger('click');

expect(wrapper.emitted('click')).toBeTruthy();
expect(wrapper.emitted('click').length).toBe(1);

Для двусторонней привязки v-model проверка может выглядеть так:

const wrapper = mount(MyInputComponent, {
  vuetify,
  propsData: {
    value: 'initial'
  }
});

wrapper.find('input').setValue('new value');

expect(wrapper.emitted('input')[0]).toEqual(['new value']);

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


Тестирование условного рендера и стилей

Многие Vuetify компоненты используют условные классы и стили, зависящие от пропсов или внутреннего состояния. Проверка этих аспектов может быть реализована через методы classes() и attributes():

const wrapper = mount(MyCardComponent, {
  vuetify,
  propsData: {
    outlined: true
  }
});

expect(wrapper.classes()).toContain('v-card--outlined');

Для inline-стилей:

expect(wrapper.attributes('style')).toContain('background-color: red');

Такая проверка особенно полезна для компонентов с динамическими цветами, размерами и состояниями (disabled, active, loading).


Мокирование зависимостей Vuetify

В некоторых случаях полезно мокировать Vuetify компоненты для ускорения тестов и изоляции логики. Например, для v-dialog или v-menu:

jest.mock('vuetify/lib/components/VDialog', () => ({
  name: 'VDialog',
  render: h => h('div')
}));

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


Тестирование асинхронного поведения

Vuetify компоненты часто используют асинхронные переходы (transition) и анимации. Для корректного тестирования необходимо использовать nextTick:

wrapper.find('button').trigger('click');
await Vue.nextTick();

expect(wrapper.find('.menu').isVisible()).toBe(true);

Если компонент управляет внутренними таймерами или задержками, можно применять Jest Fake Timers:

jest.useFakeTimers();
wrapper.find('button').trigger('click');
jest.runAllTimers();
await Vue.nextTick();

Практика тестирования сложных компонентов

Примеры компонентов, требующих комплексного подхода:

  • v-data-table: проверка фильтрации, сортировки и отображения слотов.
  • v-autocomplete / v-select: тестирование ввода, раскрытия списка и выделения элемента.
  • v-dialog / v-menu: проверка открытия, закрытия и событий keydown/click.

Для таких компонентов важно сочетать проверку рендера, событий, слотов и условных стилей, чтобы unit-тест действительно покрывал все функциональные аспекты.


Практические советы

  • Изолировать Vuetify: всегда создавать новый экземпляр Vuetify в beforeEach.
  • Использовать shallowMount для сокращения тестов, если глубина дочерних компонентов не важна.
  • Проверять события через emitted(), а не через DOM.
  • Тестировать динамические пропсы и реакции на их изменение.
  • Мокировать transitions, если визуальные анимации мешают синхронным тестам.
  • Комбинировать snapshot-тесты и функциональные проверки для комплексных компонентов.

Unit-тестирование компонентов Vuetify — это баланс между проверкой визуального рендера, реактивного состояния и событийного взаимодействия. Правильная организация тестов повышает надежность UI и облегчает поддержку сложных интерфейсов на Vue.