Как тестировать Vue-компоненты?

Тестирование Vue-компонентов позволяет удостовериться, что поведение и рендеринг компонентов соответствуют ожиданиям. Для этого обычно используют такие инструменты, как Vue Test Utils, Jest, Vitest или Cypress — в зависимости от типа тестов: unit (модульные), integration (интеграционные), e2e (сквозные).

Основные виды тестов

  1. Unit-тесты — изолированно проверяют поведение одного компонента.

  2. Integration-тесты — проверяют взаимодействие компонентов между собой.

  3. E2E-тесты — проверяют поведение приложения в браузере с точки зрения пользователя.

Установка инструментов

Для unit-тестирования Vue чаще всего используют комбинацию Vue Test Utils + Jest или Vitest.

Пример установки с Vitest:

npm install -D vitest vue-test-utils @testing-library/vue jsdom

Структура типичного теста

Пример компонента Counter.vue:

<template>
<div>
<p data-testid="count">Count: {{ count }}</p>
<button @click="increment">+</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
const increment = () => count.value++;
</script>

Тест с использованием Vue Test Utils + Vitest:

// Counter.test.ts
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import Counter from './Counter.vue';
describe('Counter.vue', () => {
it('начальное значение равно 0', () => {
const wrapper = mount(Counter);
expect(wrapper.text()).toContain('Count: 0');
});
it('при клике значение увеличивается', async () => {
const wrapper = mount(Counter);
await wrapper.find('button').trigger('click');
expect(wrapper.text()).toContain('Count: 1');
});
});

Тестирование props

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

Пример компонента Hello.vue:

<template>
<h1>Hello, {{ name }}!</h1>
</template>
<script setup>
defineProps({ name: String });
</script>

Тест:

import { mount } from '@vue/test-utils';
import Hello from './Hello.vue';
it('отображает имя из props', () => {
const wrapper = mount(Hello, {
props: { name: 'Алиса' }
});
expect(wrapper.text()).toBe('Hello, Алиса!');
});

Тестирование событий (emits)

Если компонент вызывает события через emit, это тоже нужно тестировать.

Пример Child.vue:

<template>
<button @click="$emit('custom-click')">Click me</button>
</template>

Тест:

import { mount } from '@vue/test-utils';
import Child from './Child.vue';
it('эмитит событие custom-click', async () => {
const wrapper = mount(Child);
await wrapper.find('button').trigger('click');
expect(wrapper.emitted()).toHaveProperty('custom-click');
});

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

Если компонент принимает слоты, это также можно протестировать.

Пример Card.vue:

<template>
<div class="card">
<slot />
</div>
</template>

Тест:

import { mount } from '@vue/test-utils';
import Card from './Card.vue';
it('рендерит слот', () => {
const wrapper = mount(Card, {
slots: {
default: '<p>Контент</p>'
}
});
expect(wrapper.html()).toContain('<p>Контент</p>');
});

Моки: API, Vue Router, Vuex/Pinia

При интеграции с API, роутером или стором желательно создавать моки, чтобы не зависеть от внешнего окружения.

Пример мок Axios:

vi.mock('axios', () => ({
default: {
get: vi.fn(() => Promise.resolve({ data: \[1, 2, 3\] }))
}
}));

Тестирование с Vue Router

Если компонент использует Vue Router, необходимо обернуть его в router:

import { createRouter, createMemoryHistory } from 'vue-router';
const router = createRouter({
history: createMemoryHistory(),
routes: \[{ path: '/', component: Home }\]
});
const wrapper = mount(MyComponent, {
global: {
plugins: \[router\]
}
});

Тестирование Vuex

Для старых проектов с Vuex:

import { createStore } from 'vuex';
const store = createStore({
state() {
return { count: 0 };
},
mutations: {
increment(state) {
state.count++;
}
}
});
const wrapper = mount(MyComponent, {
global: {
plugins: \[store\]
}
});

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

Компоненты на Composition API тестируются так же, как и обычные. Vue Test Utils обрабатывает ref, computed, watch и onMounted как часть setup().

Использование Testing Library (альтернатива Test Utils)

@testing-library/vue предоставляет более user-centric подход. Он ищет элементы по тексту, role, label, а не по структуре DOM.

import { render, fireEvent } from '@testing-library/vue';
import Counter from './Counter.vue';
it('увеличивает значение при клике', async () => {
const { getByText } = render(Counter);
const button = getByText('+');
await fireEvent.click(button);
getByText('Count: 1');
});

E2E-тесты: Cypress

Cypress используется для тестирования всего приложения в браузере.

Установка:

npm install -D cypress

Пример:

// cypress/e2e/app.cy.js
describe('Главная страница', () => {
it('отображает заголовок', () => {
cy.visit('/');
cy.contains('Добро пожаловать');
});
});