Тестирование guard'ов

Guard’ы (маршрутизаторы и навигационные охранники) играют ключевую роль в управлении переходами между маршрутами во Vue-приложениях. Правильное тестирование guard’ов обеспечивает надежность логики навигации, предотвращает доступ к запрещенным страницам и позволяет корректно обрабатывать условия перехода. В экосистеме Vue Test Utils процесс тестирования guard’ов требует внимания к контексту маршрутизатора и состоянию приложения.


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

Для начала необходимо настроить Vue Router в тестовой среде. Обычно используются функции createRouter и createWebHistory (или createMemoryHistory для тестов), чтобы эмулировать поведение маршрутов без зависимости от браузера.

import { mount } from '@vue/test-utils';
import { createRouter, createMemoryHistory } from 'vue-router';
import MyComponent from '@/components/MyComponent.vue';

const routes = [
  { path: '/', component: MyComponent },
  { path: '/protected', component: MyComponent, meta: { requiresAuth: true } }
];

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

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


Тестирование глобальных beforeEach guard

Глобальные guard’ы, такие как router.beforeEach, часто используются для проверки авторизации или условий доступа. Тестирование этих guard’ов сводится к имитации состояния приложения и проверке правильности редиректа или разрешения перехода.

router.beforeEach((to, from, next) => {
  const isAuthenticated = false;
  if (to.meta.requiresAuth && !isAuthenticated) {
    next('/');
  } else {
    next();
  }
});

test('редирект на / если пользователь не авторизован', async () => {
  router.push('/protected');
  await router.isReady();

  expect(router.currentRoute.value.fullPath).toBe('/');
});

Ключевые моменты:

  • Использование router.isReady() гарантирует, что асинхронная навигация завершена.
  • router.push() инициирует переход к маршруту.
  • Проверка router.currentRoute.value позволяет убедиться, что guard корректно отработал.

Тестирование локальных beforeEnter guard

Локальные guard’ы назначаются на конкретные маршруты и проверяются аналогично глобальным, но с фокусом на отдельный маршрут.

const routes = [
  { 
    path: '/admin', 
    component: MyComponent, 
    beforeEnter: (to, from, next) => {
      const isAdmin = false;
      isAdmin ? next() : next('/');
    }
  }
];

test('локальный guard перенаправляет неадмина', async () => {
  router.push('/admin');
  await router.isReady();

  expect(router.currentRoute.value.fullPath).toBe('/');
});

Особенность тестирования локальных guard’ов:

  • Они вызываются автоматически при попытке навигации на соответствующий маршрут.
  • Проверка редиректа осуществляется через состояние currentRoute.

Тестирование асинхронных guard

Guard’ы могут содержать асинхронную логику, например, запросы к API для проверки прав доступа. В таких случаях необходимо использовать async/await в тестах и ожидать завершения навигации.

router.beforeEach(async (to, from, next) => {
  const hasAccess = await checkAccess(to.path);
  hasAccess ? next() : next('/');
});

test('асинхронный guard корректно блокирует доступ', async () => {
  router.push('/protected');
  await router.isReady();

  expect(router.currentRoute.value.fullPath).toBe('/');
});

Ключевые моменты:

  • Любая асинхронная логика должна быть полностью завершена до проверки состояния маршрута.
  • await router.isReady() синхронизирует тест с навигацией.

Тестирование компонентов с использованием guard’ов

Иногда важно проверять, что guard влияет на поведение компонентов. Для этого Vue Test Utils позволяет монтировать компоненты с интегрированным маршрутизатором.

import { mount } from '@vue/test-utils';

test('компонент не рендерится при блокировке guard', async () => {
  const wrapper = mount(MyComponent, {
    global: {
      plugins: [router]
    }
  });

  router.push('/protected');
  await router.isReady();

  expect(wrapper.html()).toBe('');
});

Принципы:

  • Компонент монтируется с плагином router.
  • Если guard перенаправляет, компонент не рендерится или рендерится другой маршрут.
  • Проверка wrapper.html() позволяет убедиться, что содержимое корректно отрендерено или отсутствует.

Тестирование метаданных маршрутов

Часто guard’ы используют данные из meta маршрута для определения условий перехода. Тестирование должно учитывать разные комбинации этих данных.

const routes = [
  { path: '/vip', component: MyComponent, meta: { requiresVIP: true } }
];

router.beforeEach((to, from, next) => {
  const isVIP = false;
  to.meta.requiresVIP && !isVIP ? next('/') : next();
});

test('meta guard перенаправляет не VIP пользователя', async () => {
  router.push('/vip');
  await router.isReady();

  expect(router.currentRoute.value.fullPath).toBe('/');
});

Принципы:

  • Guard проверяет метаданные маршрута.
  • Тест должен эмулировать состояние, которое влияет на переход.
  • Проверка навигации проводится через currentRoute.

Важные рекомендации

  1. Изоляция тестов — каждый тест должен использовать отдельный экземпляр маршрутизатора или очищать его состояние, чтобы предотвратить зависимость между тестами.
  2. Асинхронные guard’ы — всегда ожидать завершения навигации через router.isReady() или nextTick().
  3. Комбинации guard’ов — проверять взаимодействие глобальных и локальных guard’ов, особенно если они могут вызывать редирект одновременно.
  4. Проверка редиректов — основным способом проверки guard’ов является сравнение текущего маршрута с ожидаемым значением.

Тестирование guard’ов с Vue Test Utils и Vue Router обеспечивает надежность навигационной логики и позволяет выявлять ошибки доступа на раннем этапе разработки. Правильная организация тестов, внимание к асинхронности и метаданным маршрутов делают тесты стабильными и воспроизводимыми.