Какие подходы к тестированию вы считаете лучшими для Vue (unit, e2e, snapshot)?

Тестирование во Vue-приложениях охватывает несколько уровней: unit-тесты, snapshot-тесты, интеграционные и end-to-end (e2e). Эффективная стратегия включает все эти типы, каждый из которых решает конкретные задачи. Выбор подходов зависит от размера проекта, команды, требований к стабильности и CI/CD-процесса. Ниже приведены лучшие практики и технические детали для каждого вида тестов применительно к Vue, преимущественно на базе Vue 3, Vite и современных тулов (Vitest, Vue Test Utils, Cypress, Playwright).

1. Unit-тесты

Цель:

Проверка изолированной логики компонентов, composables, Vuex/Pinia-сторов и функций без участия DOM или сетевых вызовов.

Инструменты:

  • vitest — быстрый, совместим с Vite

  • @vue/test-utils — от команды Vue

  • jest — популярен, но медленнее на Vite-проектах

Структура:

// Button.spec.ts
import { mount } from '@vue/test-utils'
import UiButton from '@/components/ui/UiButton.vue'
describe('UiButton', () => {
it('рендерит слот', () => {
const wrapper = mount(UiButton, {
slots: {
default: 'Кнопка'
}
})
expect(wrapper.text()).toContain('Кнопка')
})
it('отправляет событие при клике', async () => {
const wrapper = mount(UiButton)
await wrapper.trigger('click')
expect(wrapper.emitted()).toHaveProperty('click')
})
})

Best Practices:

  • Использовать shallowMount для крупных компонентов

  • Мокать зависимости (например, API)

  • Проверять props, emits, computed, watch

  • Компоненты должны быть testable по SRP (Single Responsibility Principle)

2. Snapshot-тесты

Цель:

Проверка неизменности шаблонов компонентов между коммитами.

Используется для:

  • UI-компонентов с большим количеством вариантов (card, button, input)

  • Предотвращения случайных изменений в DOM

Пример:

import { mount } from '@vue/test-utils'
import UiCard from '@/components/ui/UiCard.vue'
test('snapshot', () => {
const wrapper = mount(UiCard, {
props: { title: 'Заголовок' }
})
expect(wrapper.html()).toMatchSnapshot()
})

Минусы:

  • Хрупкость (мелкие изменения ломают снапшот)

  • Сложно поддерживать без автосогласования

Best Practices:

  • Делать снапшоты на уровне атомарных компонентов

  • Не использовать снапшоты для динамически меняющихся блоков (например, v-if, v-for)

  • Не хранить снапшоты больших страниц

3. E2E-тесты (end-to-end)

Цель:

Проверка полной пользовательской истории в браузере с взаимодействием с бэкендом и DOM.

Инструменты:

  • Cypress — простой, мощный, быстрый

  • Playwright — альтернатива с мультибраузерной поддержкой

  • [Nightwatch, TestCafe, Selenium] — менее актуальны

Пример (Cypress):

describe('Auth flow', () => {
it('логин и переход в дашборд', () => {
cy.visit('/login')
cy.get('input\[name=email\]').type('admin@test.com')
cy.get('input\[name=password\]').type('123456')
cy.get('button\[type=submit\]').click()
cy.url().should('include', '/dashboard')
cy.contains('Добро пожаловать')
})
})

Подходы:

  • data-testid для селекторов (не использовать классы/тексты)

  • Стабильное тестовое API или мок-сервер (например, msw)

  • Изолированная база данных для CI

  • Параллельный запуск e2e (Cypress Cloud, GitHub Actions matrix)

Best Practices:

  • Покрывать только критические пользовательские сценарии

  • Делать smoke-тесты на каждую deploy-сборку

  • Удалять flaky-тесты или выносить в quarantine

  • Использовать cy.intercept() для моков

4. Интеграционные тесты

Цель:

Проверка связки нескольких компонентов или взаимодействия с внешними зависимостями (store, router, API).

Пример:

import { mount } from '@vue/test-utils'
import ProductCard from '@/features/catalog/ProductCard.vue'
import { createPinia, setActivePinia } from 'pinia'
beforeEach(() => {
setActivePinia(createPinia())
})
test('отображает товар из стора', () => {
const wrapper = mount(ProductCard, {
global: {
plugins: \[createPinia()\]
}
})
expect(wrapper.find('.price').text()).toContain('₽')
})

Подключение роутера:

import { createRouter, createWebHistory } from 'vue-router'
import routes from '@/router'
const router = createRouter({ history: createWebHistory(), routes })
mount(MyComponent, {
global: {
plugins: \[router\]
}
})

5. Тестирование composables

Композиционные функции (useForm(), useDebounce()) тестируются как обычные JS-функции.

import { useCounter } from '@/composables/useCounter'
test('инкремент', () => {
const { count, increment } = useCounter()
expect(count.value).toBe(0)
increment()
expect(count.value).toBe(1)
})

Если требуется lifecycle (onMounted, watch), использовать @vue/test-utils с setup()-вызовом.

6. Покрытие кода

Инструменты:

  • vitest --coverage

  • c8 (на базе V8)

  • nyc (при использовании jest)

Цель:

  • Отслеживание, какие участки кода покрыты

  • Анализ покрытия ветвлений и условий

Best Practices:

  • Target: 80–90% покрытия

  • Отдельный ci job в GitHub Actions или GitLab для покрытия

  • Использовать Codecov/SonarCloud/Codecov badge

7. Использование mocks и stubs

Мок внешнего API:

vi.mock('@/api/user', () => ({
fetchUser: vi.fn(() => Promise.resolve({ name: 'Alex' }))
}))

Стаб компонента:

mount(MyComponent, {
global: {
stubs: {
'router-link': true
}
}
})

Позволяет изолировать компонент от зависимостей и повысить стабильность тестов.

8. Автоматизация в CI

GitHub Actions:

\- name: Run Unit Tests
run: npm run test:unit
\- name: Run E2E Tests
run: npm run test:e2e

GitLab:

test:
stage: test
script:
\- npm ci
\- npm run test

Тесты должны быть частью PR-валидации и release-пайплайна.

9. Типы покрываемых тестами сущностей

Сущность Unit Integration E2E
UI-компоненты (Button)
--- --- --- ---
Composables
--- --- --- ---
Features (Forms, Filters)
--- --- --- ---
Pages
--- --- --- ---
Бизнес-потоки
--- --- --- ---

10. Организация структуры тестов

src/
├── components/
 └── UiButton.vue
 └── \__tests_\_/UiButton.spec.ts
├── features/
 └── login/
 ├── LoginForm.vue
 └── \__tests_\_/LoginForm.spec.ts
├── composables/
 └── useModal.ts
 └── \__tests_\_/useModal.spec.ts
tests/
├── e2e/
 └── auth.cy.ts
├── setup.ts

11. Локальный запуск с Vite/Vitest

npx vitest --ui
  • Позволяет запускать тесты в браузере с hot reload

  • Быстро, особенно на Vite-базе

12. Рекомендованные зависимости

  • @vue/test-utils

  • vitest или jest

  • happy-dom или jsdom

  • cypress или playwright

  • @testing-library/vue (альтернатива для user-centric тестов)

  • msw (для моков API)