Какие подходы к тестированию вы считаете лучшими для 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)