Моки роутера для unit-тестов

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


Подключение Vue Router к тестам

Для начала необходимо импортировать сам Vue Router и вспомогательные функции тестирования:

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

Создание мок-роутера подразумевает настройку маршрутов в минимальном виде:

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

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

В unit-тестах часто не требуется настоящая навигация, поэтому маршрутизатор можно замокать полностью, используя только ключевые методы: push, replace, currentRoute.


Мок методов роутера

push и replace — методы, используемые для перехода по маршрутам. В тестах их удобно заменять на jest-функции:

const mockRouter = {
  push: jest.fn(),
  replace: jest.fn(),
  currentRoute: { value: { path: '/' } }
};

Такой мок позволяет проверять, какие маршруты пытается вызвать компонент:

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

wrapper.find('button.navigate').trigger('click');

expect(mockRouter.push).toHaveBeenCalledWith({ name: 'About' });

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


Мок текущего маршрута

$route содержит информацию о текущем маршруте. Для unit-тестов удобно создавать объект с необходимыми параметрами:

const mockRoute = {
  path: '/about',
  name: 'About',
  params: { id: '123' },
  query: { ref: 'test' }
};

const wrapper = mount(MyComponent, {
  global: {
    mocks: {
      $route: mockRoute
    }
  }
});

Теперь внутри компонента $route.path вернёт /about, $route.params.id'123', что позволяет тестировать условную логику на основе текущего маршрута.


Использование composable useRouter и useRoute

В Vue 3 с Composition API вместо $router и $route часто используют useRouter и useRoute. Для их мокирования создаются заглушки:

import * as vueRouter from 'vue-router';

jest.spyOn(vueRouter, 'useRouter').mockReturnValue(mockRouter);
jest.spyOn(vueRouter, 'useRoute').mockReturnValue(mockRoute);

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


Мокаем навигацию и реактивность

Чтобы тестировать реакцию компонента на изменения маршрута, $route можно сделать реактивным:

import { ref } from 'vue';

const routeRef = ref({
  path: '/',
  params: {},
  query: {}
});

const mockRouteReactive = {
  value: routeRef.value
};

const wrapper = mount(MyComponent, {
  global: {
    mocks: {
      $route: mockRouteReactive
    }
  }
});

// Изменение маршрута в тесте
routeRef.value = { path: '/new', params: {}, query: {} };

expect(wrapper.text()).toContain('/new');

Такой подход позволяет проверять watch и computed свойства, зависящие от текущего маршрута.


Практика мокирования с createRouter

Для интеграционных unit-тестов, когда нужен реальный роутер, но без полной истории браузера, можно использовать createRouter с memory history:

import { createMemoryHistory } from 'vue-router';

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

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

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

expect(wrapper.html()).toContain('About Page');

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


Тонкости тестирования с моками

  • push и replace должны возвращать промис, если компонент использует await $router.push().
  • Для тестирования навигации с параметрами и query стоит мокировать объект $route с полной структурой params и query.
  • Если компонент использует router-link, можно заменить его на простой stub или мок:
global: {
  stubs: {
    RouterLink: { template: '<a><slot /></a>' }
  }
}
  • Реактивность $route особенно важна при тестировании watch на route или computed с route.params.

Примеры использования в Jest

test('navigate to About on button click', async () => {
  const mockRouter = { push: jest.fn() };
  const wrapper = mount(MyComponent, {
    global: { mocks: { $router: mockRouter } }
  });

  await wrapper.find('button.navigate').trigger('click');
  expect(mockRouter.push).toHaveBeenCalledWith({ name: 'About' });
});

test('renders data based on route param', () => {
  const mockRoute = { params: { id: '42' } };
  const wrapper = mount(MyComponent, {
    global: { mocks: { $route: mockRoute } }
  });

  expect(wrapper.text()).toContain('42');
});

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