Тестирование навигационных хуков

Навигационные хуки Vue Router — это специальные функции, которые выполняются в процессе перехода между маршрутами. Они позволяют управлять доступом к страницам, выполнять асинхронные операции перед рендерингом компонентов, а также контролировать поведение маршрутизатора в целом. Тестирование этих хуков является ключевым для обеспечения корректной работы маршрутизации в приложении.


Типы навигационных хуков

Vue Router предоставляет несколько видов хуков:

  1. Global Before Hooks (router.beforeEach) Выполняются перед каждым переходом маршрута. Позволяют проверять условия доступа, загружать данные или перенаправлять пользователя.

  2. Global Resolve Hooks (router.beforeResolve) Срабатывают после всех beforeEach и перед финальной загрузкой компонента. Часто используются для окончательной подготовки данных перед отображением.

  3. Global After Hooks (router.afterEach) Выполняются после завершения перехода маршрута. Не могут отменять навигацию, но позволяют, например, обновлять метаданные страницы.

  4. Per-Route Guards (beforeEnter) Определяются на уровне конкретного маршрута. Предназначены для проверки условий доступа или подготовки данных для конкретного пути.

  5. In-Component Guards (beforeRouteEnter, beforeRouteUpdate, beforeRouteLeave) Определяются внутри компонентов и дают возможность контролировать поведение при входе, обновлении или уходе с маршрута. Отличие в том, что beforeRouteEnter не имеет доступа к экземпляру компонента, пока он не будет создан.


Основные подходы к тестированию

Мокирование маршрутизатора

Для тестирования навигационных хуков важно имитировать поведение Vue Router без необходимости полноценного запуска всего приложения. Для этого создается мок объекта router, с которым можно:

  • Вызывать хуки вручную, передавая фиктивные to и from объекты маршрутов.
  • Подменять методы навигации (push, replace) для проверки перенаправлений.

Пример структуры мока:

const mockRouter = {
  push: jest.fn(),
  replace: jest.fn(),
  beforeEachHooks: [],
  beforeEach(fn) {
    this.beforeEachHooks.push(fn);
  }
};

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

Глобальные хуки требуют проверки логики в различных сценариях переходов:

  1. Вызов хуков при навигации Для этого можно имитировать объекты маршрутов to и from:
const to = { path: '/dashboard', meta: { requiresAuth: true } };
const from = { path: '/login' };

let nextCalled = false;
router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth) {
    nextCalled = true;
  }
});

router.beforeEachHooks.forEach(hook => hook(to, from, () => {}));
expect(nextCalled).toBe(true);
  1. Проверка перенаправлений Если в хуке предусмотрено перенаправление:
router.beforeEach((to, from, next) => {
  if (!isAuthenticated()) next('/login');
  else next();
});

const nextMock = jest.fn();
hook(to, from, nextMock);
expect(nextMock).toHaveBeenCalledWith('/login');

Тестирование пер-роутов и ин-компонент хуков

Для per-route guards достаточно вызвать функцию beforeEnter напрямую, передав моки маршрутов и next. Ин-компонентные хуки тестируются чаще всего через монтирование компонента с использованием Vue Test Utils, где можно:

  • Подменять роутер и имитировать переходы.
  • Проверять вызовы методов next и изменение состояния компонента.

Пример для beforeRouteLeave:

const wrapper = mount(MyComponent, { global: { mocks: { $router: mockRouter } } });
const leaveHook = wrapper.vm.$options.beforeRouteLeave[0];

const nextMock = jest.fn();
leaveHook.call(wrapper.vm, { path: '/home' }, { path: '/dashboard' }, nextMock);
expect(nextMock).toHaveBeenCalled();

Асинхронные операции и тестирование

Навигационные хуки часто используют асинхронные операции, например, проверку авторизации через API. Для тестов это важно:

  1. Использовать async/await или возвращать Promise в хуках.
  2. Подставлять мокированные функции API для предсказуемого результата.
  3. Проверять поведение next после завершения асинхронной операции.
router.beforeEach(async (to, from, next) => {
  const user = await fetchUser();
  if (!user) next('/login');
  else next();
});

В тесте можно сделать:

jest.mock('./api', () => ({ fetchUser: jest.fn(() => Promise.resolve(null)) }));

await hook(to, from, nextMock);
expect(nextMock).toHaveBeenCalledWith('/login');

Стратегии организации тестов

  • Разделение по типу хука: отдельные файлы для глобальных, per-route и in-component guards.
  • Использование моков маршрутов: единый набор фиктивных to и from объектов для разных сценариев.
  • Проверка ветвлений: тестировать как успешный переход, так и перенаправления.
  • Асинхронные сценарии: включать тесты с задержками и промисами, чтобы убедиться, что next вызывается корректно.

Полезные советы

  • В хуках beforeRouteEnter нельзя обращаться к this, поэтому проверку состояния компонента нужно делать через колбек next(vm => { ... }).
  • Для хуков beforeRouteUpdate важно тестировать как обновление параметров, так и смену query-параметров.
  • В beforeRouteLeave можно тестировать предотвращение ухода, передавая false в next.

Тщательное тестирование навигационных хуков позволяет гарантировать, что переходы между страницами работают корректно, а бизнес-логика, связанная с авторизацией, загрузкой данных и перенаправлениями, всегда выполняется предсказуемо.