Мокирование роутера

Мокирование роутера в библиотеке Page.js используется для имитации поведения навигации в приложении без реальной загрузки страниц или изменения URL в браузере. Это особенно полезно при тестировании компонентов, где важна логика маршрутизации, но нет необходимости в полноценном браузерном окружении.


Основы маршрутизации Page.js

Page.js — это небольшая клиентская библиотека для маршрутизации в одностраничных приложениях (SPA). Она работает по принципу сопоставления URL с обработчиками:

page('/', () => console.log('Главная страница'));
page('/about', () => console.log('Страница о нас'));
page.start();

При вызове page.start() библиотека начинает отслеживать изменения URL и вызывает соответствующие функции-обработчики.

Для мокирования ключевым является понимание, что Page.js управляет двумя основными вещами:

  1. История браузера (history API) — pushState, replaceState.
  2. Обработчики маршрутов — функции, привязанные к определённым путям.

Мокирование позволяет перехватывать эти действия, чтобы контролировать тестируемое поведение без реальных переходов по URL.


Подходы к мокированию

  1. Подмена функции page() и page.start()
import page from 'page';

jest.mock('page', () => {
  const routes = {};
  return {
    __esModule: true,
    default: (path, handler) => {
      routes[path] = handler;
    },
    start: jest.fn(),
    navigate: (path) => {
      if (routes[path]) routes[path]();
    },
  };
});

В этом примере:

  • routes хранит сопоставление URL → обработчик.
  • start заменён на мок-функцию.
  • Метод navigate имитирует переход по маршруту без изменения URL браузера.

Это позволяет тестировать вызовы обработчиков при переходах:

import page from 'page';

test('переход на главную страницу вызывает нужный обработчик', () => {
  const handler = jest.fn();
  page('/', handler);
  
  page.navigate('/');
  
  expect(handler).toHaveBeenCalled();
});

  1. Мокирование history API

Для более низкоуровневого контроля можно заменить методы window.history:

beforeEach(() => {
  jest.spyOn(window.history, 'pushState').mockImplementation(() => {});
  jest.spyOn(window.history, 'replaceState').mockImplementation(() => {});
});

afterEach(() => {
  jest.restoreAllMocks();
});

Таким образом, любые вызовы page.redirect() или page.show() не будут изменять URL, но сохранят логику маршрутизации для проверки.


Тестирование переходов с параметрами

Page.js поддерживает динамические маршруты:

page('/user/:id', ctx => console.log(`Пользователь ${ctx.params.id}`));

При мокировании важно правильно формировать объект ctx:

const ctx = { path: '/user/42', params: { id: '42' } };
routes['/user/:id'](ctx);

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


Использование фреймворков для мокирования

  • Jest: позволяет создавать мок-функции и шпионов для методов Page.js и history API.
  • Sinon.js: обеспечивает контроль вызовов и позволяет восстанавливать оригинальные функции после теста.

Пример с Sinon:

import sinon from 'sinon';
import page from 'page';

const startStub = sinon.stub(page, 'start');
const handlerSpy = sinon.spy();

page('/test', handlerSpy);
page.navigate('/test');

sinon.assert.calledOnce(handlerSpy);
startStub.restore();

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

  • Изоляция тестов: каждый тест должен использовать отдельный мок роутера, чтобы предыдущие маршруты не влияли на следующие.
  • Контроль ctx: имитация контекста (ctx) позволяет тестировать передачу параметров, query-параметров и состояния.
  • Минимизация side-effects: мокирование history предотвращает непреднамеренные изменения URL и перезагрузки страницы.
  • Проверка вызовов redirect/show: важно тестировать не только обработку маршрутов, но и корректность редиректов внутри SPA.

Практический пример комплексного мокирования

import page from 'page';

const routes = {};
const navigateMock = (path) => {
  const routeKeys = Object.keys(routes);
  const match = routeKeys.find(k => k.includes(':') || k === path);
  if (match) {
    const ctx = { path, params: { id: path.split('/')[2] } };
    routes[match](ctx);
  }
};

jest.mock('page', () => ({
  __esModule: true,
  default: (path, handler) => { routes[path] = handler; },
  start: jest.fn(),
  navigate: navigateMock,
}));

page('/user/:id', ctx => console.log(ctx.params.id));

page.navigate('/user/123'); // выведет 123

Такой подход обеспечивает полное мокирование роутера для тестирования SPA без реального DOM и URL, сохраняя всю функциональность Page.js для unit-тестов.