Тестирование навигации

Основы навигации в Vue

В приложениях на Vue, использующих Vue Router, навигация управляется объектом router. Основные способы перехода между маршрутами — это использование компонентов <router-link> и программная навигация через методы router.push, router.replace и router.go. Для тестирования корректности переходов важно симулировать эти действия и проверять состояние маршрутизатора после них.

Подготовка окружения для тестирования

Для тестирования навигации необходим реальный или моковый экземпляр маршрутизатора. Чаще всего используют createRouter с режимом memory (для Vue Router 4) для изоляции тестов:

import { createRouter, createMemoryHistory } from 'vue-router'
import { mount } from '@vue/test-utils'
import App from '@/App.vue'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'

const routes = [
  { path: '/', name: 'Home', component: Home },
  { path: '/about', name: 'About', component: About },
]

const router = createRouter({
  history: createMemoryHistory(),
  routes,
})

const wrapper = mount(App, {
  global: {
    plugins: [router]
  }
})

Использование createMemoryHistory позволяет тестам работать в изолированной памяти, без реального изменения URL браузера.

Компоненты <router-link> создают ссылки для навигации. Проверка включает:

  1. Правильность атрибута href.
  2. Корректный переход по клику.

Пример теста:

import { nextTick } from 'vue'

const wrapper = mount(App, {
  global: { plugins: [router] }
})

// Проверка атрибута href
const link = wrapper.find('a[href="/about"]')
expect(link.exists()).toBe(true)

// Симуляция клика
await link.trigger('click')
await router.isReady() // дождаться завершения навигации
expect(router.currentRoute.value.name).toBe('About')

router.isReady() гарантирует, что переход завершён, прежде чем проверять currentRoute.

Тестирование программной навигации

Для компонентов, использующих router.push или router.replace:

const wrapper = mount(MyComponent, {
  global: { plugins: [router] }
})

// Вызов метода навигации компонента
await wrapper.vm.goToAbout()
await router.isReady()

expect(router.currentRoute.value.name).toBe('About')

Здесь важно проверять не только имя маршрута, но и путь или параметры, если они используются.

Тестирование параметров маршрута

Многие маршруты содержат динамические параметры:

{ path: '/user/:id', name: 'User', component: User }

Для тестирования необходимо передать параметры при навигации:

await router.push({ name: 'User', params: { id: '42' } })
await router.isReady()

expect(router.currentRoute.value.params.id).toBe('42')

Для <router-link> с параметрами:

const link = wrapper.findComponent({ ref: 'userLink' })
await link.trigger('click')
await router.isReady()
expect(router.currentRoute.value.params.id).toBe('42')

Проверка переходов с query-параметрами

Query-параметры передаются через объект query:

await router.push({ path: '/about', query: { ref: 'newsletter' } })
await router.isReady()

expect(router.currentRoute.value.query.ref).toBe('newsletter')

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

Моки маршрутизатора для изоляции компонентов

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

const mockRouter = {
  push: jest.fn(),
  replace: jest.fn(),
}

const wrapper = mount(MyComponent, {
  global: { mocks: { $router: mockRouter } }
})

wrapper.vm.goToAbout()
expect(mockRouter.push).toHaveBeenCalledWith({ name: 'About' })

Использование моков ускоряет тесты и исключает необходимость полной инициализации маршрутизатора.

Асинхронность навигации

Все переходы во Vue Router асинхронны. Для корректного тестирования важно использовать await router.isReady() после push/replace и await nextTick() при необходимости обновления DOM. Без этих шагов тесты могут завершаться до завершения перехода, что приведёт к ложноположительным или ложноотрицательным результатам.

Проверка активных ссылок

Компоненты <router-link> добавляют класс router-link-active при совпадении маршрута. Тестирование активного состояния:

const link = wrapper.find('a[href="/about"]')
expect(link.classes()).toContain('router-link-active')

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

Навигация с редиректами и guards

Маршруты могут иметь редиректы или beforeEnter guards. Для тестирования важно учитывать, что переход может быть отменён или перенаправлен:

const routes = [
  { path: '/secret', redirect: '/login' },
]

await router.push('/secret')
await router.isReady()
expect(router.currentRoute.value.path).toBe('/login')

Также можно проверять вызовы guard-функций через моки:

const guard = jest.fn((to, from, next) => next())
{ path: '/protected', beforeEnter: guard }

await router.push('/protected')
expect(guard).toHaveBeenCalled()

Проверка навигации с программной историей

Методы router.go(n) позволяют эмулировать действия истории браузера. Для тестов используют router.push и router.back:

await router.push('/about')
await router.push('/contact')
await router.back()
await router.isReady()
expect(router.currentRoute.value.path).toBe('/about')

Это полезно для проверки кнопок «назад» и поведения истории.

Рекомендации по структуре тестов

  • Каждое поведение навигации тестируется отдельно: клики, программные вызовы, редиректы, guards.
  • Использовать createMemoryHistory для изоляции.
  • Всегда дожидаться завершения переходов через router.isReady() или nextTick.
  • При юнит-тестировании компонентов можно мокировать $router и $route.
  • Проверять как путь, так и имя маршрута, параметры и query.