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

Universal Router — это гибкий маршрутизатор для JavaScript, который работает как на сервере, так и в браузере. Его ключевая особенность — декларативное определение маршрутов через объект или массив объектов, где каждый маршрут описывается с помощью путя (path), обработчика (action) и опциональных вложенных маршрутов (children).

Структура маршрута:

{
  path: '/users/:id',
  action: ({ params }) => `Пользователь с ID ${params.id}`,
  children: [
    {
      path: 'edit',
      action: ({ params }) => `Редактирование пользователя ${params.id}`
    }
  ]
}
  • path — строка с поддержкой динамических сегментов (:id) и регулярных выражений.
  • action — функция, вызываемая при совпадении маршрута, принимает объект с параметрами, базовым URL и контекстом.
  • children — вложенные маршруты, которые позволяют строить иерархию маршрутизации.

Инициализация и базовое использование

Создание маршрутизатора требует передачи массива маршрутов в конструктор UniversalRouter.

import UniversalRouter from 'universal-router';

const routes = [
  {
    path: '/',
    action: () => 'Главная страница'
  },
  {
    path: '/about',
    action: () => 'О нас'
  }
];

const router = new UniversalRouter(routes);

Для разрешения маршрута используется метод resolve:

router.resolve('/about').then(result => {
  console.log(result); // "О нас"
});

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

Динамические сегменты и параметры

Динамические сегменты в path обозначаются с помощью двоеточия :paramName. Они автоматически извлекаются и передаются в action через объект params.

{
  path: '/posts/:postId',
  action: ({ params }) => `Пост с ID ${params.postId}`
}

Поддерживается также вложенная маршрутизация:

{
  path: '/posts/:postId',
  children: [
    {
      path: 'comments/:commentId',
      action: ({ params }) => `Комментарий ${params.commentId} к посту ${params.postId}`
    }
  ]
}

Асинхронные маршруты и загрузка данных

Universal Router позволяет использовать асинхронные функции в action, что удобно для загрузки данных перед отображением страницы.

{
  path: '/profile/:id',
  async action({ params }) {
    const user = await fetchUser(params.id);
    return `Профиль пользователя: ${user.name}`;
  }
}

Асинхронная маршрутизация интегрируется с промисами и async/await, что упрощает построение SPA с динамическим контентом.

Контекст маршрутизатора

context — объект, который можно передавать в resolve и который будет доступен в каждом action. Это удобно для передачи сервисов, состояния приложения или данных конфигурации.

const router = new UniversalRouter(routes);

const context = { userService, authToken: '123' };

router.resolve('/profile/42', { context }).then(result => {
  console.log(result);
});

В action можно обращаться к context напрямую:

action({ params, context }) {
  return context.userService.getUser(params.id);
}

Обработка ошибок и fallback-маршруты

Для перехвата маршрутов, которые не совпали с существующими, используют маршрут с path: '(.*)' или path: '*'.

{
  path: '(.*)',
  action: () => '404 — страница не найдена'
}

Также можно обрабатывать ошибки внутри action через try/catch или глобально через промисы:

router.resolve('/unknown')
  .catch(err => {
    console.error('Ошибка маршрутизации:', err);
  });

Вложенная маршрутизация и lazy-loading

Вложенные маршруты позволяют создавать модульные маршруты, которые подключаются только при необходимости. Это важно для оптимизации SPA.

{
  path: '/dashboard',
  action: () => 'Панель управления',
  children: [
    {
      path: 'stats',
      action: () => 'Статистика',
    },
    {
      path: 'settings',
      action: () => import('./settings.js').then(module => module.default)
    }
  ]
}

Поддержка lazy-loading через динамический импорт уменьшает начальный размер бандла.

Middleware и предобработка маршрутов

Universal Router допускает добавление промежуточной логики через обертки action. Это позволяет проверять авторизацию, логировать действия или выполнять редиректы.

function authMiddleware(action) {
  return async (context) => {
    if (!context.user) {
      throw new Error('Неавторизованный доступ');
    }
    return action(context);
  };
}

{
  path: '/admin',
  action: authMiddleware(() => 'Админ-панель')
}

Поддержка регулярных выражений

Маршруты могут использовать регулярные выражения для более сложного сопоставления:

{
  path: /^\/product\/(\d+)$/,
  action: ({ params, match }) => `Продукт с ID ${match[1]}`
}

При таком подходе match содержит массив совпадений регулярного выражения, что удобно для обработки нестандартных URL.

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

Для интеграционных тестов важно проверять:

  • корректность сопоставления URL с нужным action;
  • передачу параметров params и context;
  • обработку ошибок и fallback-маршрутов;
  • асинхронные действия и lazy-loaded модули.

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

test('маршрут /posts/:id возвращает корректный результат', async () => {
  const routes = [
    { path: '/posts/:id', action: ({ params }) => `Пост ${params.id}` }
  ];
  const router = new UniversalRouter(routes);
  
  const result = await router.resolve('/posts/10');
  expect(result).toBe('Пост 10');
});

test('неизвестный маршрут возвращает 404', async () => {
  const routes = [
    { path: '/', action: () => 'Главная' },
    { path: '(.*)', action: () => '404' }
  ];
  const router = new UniversalRouter(routes);
  
  const result = await router.resolve('/unknown');
  expect(result).toBe('404');
});

Тестирование асинхронных маршрутов и middleware требует мокирования сервисов и проверки передачи context.


Universal Router предоставляет мощный и гибкий инструмент для построения маршрутизации в любом JavaScript-приложении, обеспечивая поддержку динамических сегментов, асинхронных данных, middleware и lazy-loading, что делает его идеальным выбором для SPA и серверных приложений.