Тестирование обработчиков маршрутов

В Page.js обработчики маршрутов (route handlers) являются функциями, которые вызываются при совпадении текущего URL с определённым маршрутом. Тестирование таких обработчиков необходимо для проверки корректности логики навигации, передачи параметров и взаимодействия с DOM или сторонними сервисами.

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


Структура обработчика маршрута

Типичный обработчик маршрута в Page.js выглядит следующим образом:

page('/user/:id', (ctx) => {
    const userId = ctx.params.id;
    renderUserProfile(userId);
});

Здесь ключевыми точками для тестирования являются:

  • ctx.path — полный путь URL.
  • ctx.params — параметры маршрута.
  • ctx.querystring — строка запроса.
  • ctx.state — состояние приложения, переданное через middleware.

Юнит-тестирование обработчиков

Юнит-тестирование предполагает проверку логики функции в отрыве от реального маршрутизатора. Для этого создаётся имитация (mock) объекта ctx и проверяется результат вызова обработчика.

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

import { renderUserProfile } from './views';
import { userRouteHandler } from './routes';

jest.mock('./views', () => ({
    renderUserProfile: jest.fn(),
}));

test('userRouteHandler передаёт правильный id в renderUserProfile', () => {
    const ctx = { params: { id: '42' } };
    userRouteHandler(ctx);
    expect(renderUserProfile).toHaveBeenCalledWith('42');
});

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

  • Используется мок renderUserProfile для контроля побочного эффекта.
  • Параметры маршрута задаются вручную через объект ctx.
  • Проверяется точность передачи данных, а не реализация функций рендеринга.

Тестирование middleware

Page.js позволяет использовать цепочки middleware:

page('/dashboard', authMiddleware, dashboardHandler);

Тестирование middleware важно для проверки логики авторизации, подстановки данных в ctx.state или обработки ошибок.

Пример:

test('authMiddleware блокирует неавторизованного пользователя', () => {
    const ctx = { state: { user: null }, redirect: jest.fn() };
    authMiddleware(ctx, () => {});
    expect(ctx.redirect).toHaveBeenCalledWith('/login');
});

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


Интеграционное тестирование маршрутов

Интеграционное тестирование предполагает проверку маршрутов в связке с Page.js, включая переходы и обновление URL. Для этого можно использовать виртуальный DOM, например jsdom, чтобы эмулировать поведение браузера.

Пример:

import page from 'page';
import { dashboardHandler } from './routes';
import { renderDashboard } from './views';

jest.mock('./views', () => ({
    renderDashboard: jest.fn(),
}));

document.body.innerHTML = '<div id="app"></div>';

page('/dashboard', dashboardHandler);
page.start();

page('/dashboard');
expect(renderDashboard).toHaveBeenCalled();
expect(window.location.pathname).toBe('/dashboard');

Особенности интеграционных тестов:

  • Page.js должен быть инициализирован через page.start().
  • Проверяется корректность изменения URL (window.location.pathname).
  • Middleware и обработчики маршрутов проверяются вместе, что позволяет выявить проблемы цепочки вызовов.

Проверка параметров и query string

Page.js позволяет передавать параметры и query string. Важно тестировать их корректную обработку:

page('/search/:term', (ctx) => {
    performSearch(ctx.params.term, ctx.querystring);
});

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

test('searchHandler передаёт параметры в performSearch', () => {
    const ctx = { params: { term: 'javascript' }, querystring: 'page=2' };
    const performSearch = jest.fn();
    searchHandler(ctx, performSearch);
    expect(performSearch).toHaveBeenCalledWith('javascript', 'page=2');
});

Обработка ошибок в маршрутах

Обработчики могут бросать ошибки или возвращать промисы. Тестирование должно учитывать асинхронные сценарии:

test('asyncRouteHandler обрабатывает ошибки', async () => {
    const ctx = {};
    const next = jest.fn();
    await expect(asyncRouteHandler(ctx, next)).resolves.not.toThrow();
});

Для промисов важно использовать await или .resolves/.rejects для корректного контроля ошибок.


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

  • Разделять юнит-тесты и интеграционные тесты для чистоты тестовой архитектуры.
  • Использовать моки и заглушки для рендеринга и API-запросов.
  • Проверять не только вызовы функций, но и состояние объекта ctx после обработки.
  • Эмулировать различные URL и параметры маршрута для покрытия всех вариантов.
  • Для middleware проверять как положительные сценарии (next() вызывается), так и отрицательные (редиректы, ошибки).

Пример комплексного теста маршрута с middleware

page('/profile/:id', authMiddleware, profileHandler);

test('полный маршрут profile', () => {
    const ctx = { params: { id: '123' }, state: { user: { id: '123' } }, redirect: jest.fn() };
    const next = jest.fn();

    authMiddleware(ctx, next);
    expect(next).toHaveBeenCalled();

    profileHandler(ctx);
    expect(renderProfile).toHaveBeenCalledWith('123');
});

В этом примере проверяется взаимодействие middleware с основным обработчиком, корректная передача параметров и вызов функции рендеринга.

Тестирование маршрутов в Page.js требует аккуратного моделирования ctx, использования моков и контроля последовательности вызовов middleware и обработчиков. Это позволяет создать надёжную и предсказуемую навигацию в одностраничных приложениях.