Модульное тестирование маршрутов

Модульное тестирование маршрутов в библиотеке Universal Router позволяет проверять корректность работы маршрутизации без необходимости запускать полный веб-сервер или интерфейс пользователя. Это обеспечивает изоляцию тестов и позволяет выявлять ошибки на раннем этапе разработки. Тестирование базируется на проверке результатов работы метода resolve() и правильности обработки параметров, контекста и переходов между маршрутами.

Настройка окружения для тестирования

Для тестирования маршрутов рекомендуется использовать фреймворки, поддерживающие асинхронные тесты, такие как Jest или Mocha. Важным элементом является создание контекста маршрутизатора, который может включать в себя:

  • данные пользователя (user, roles);
  • состояние приложения (state);
  • вспомогательные функции (fetch, redirect).

Пример создания базового маршрутизатора для тестов:

import UniversalRouter from 'universal-router';

const routes = [
  { path: '/', action: () => 'Home' },
  { path: '/about', action: () => 'About' },
  { path: '/users/:id', action: ({ params }) => `User ${params.id}` }
];

const router = new UniversalRouter(routes);

Контекст можно передавать в метод resolve() для имитации реального окружения приложения:

const context = { user: { name: 'Alice' }, state: {} };
const result = await router.resolve({ pathname: '/users/42', context });

Тестирование простых маршрутов

Для простых маршрутов важно проверить, что путь правильно сопоставляется с ожидаемой функцией action и возвращает корректное значение.

test('должен возвращать Home для корневого пути', async () => {
  const result = await router.resolve({ pathname: '/' });
  expect(result).toBe('Home');
});

test('должен возвращать About для /about', async () => {
  const result = await router.resolve({ pathname: '/about' });
  expect(result).toBe('About');
});

Тестирование маршрутов с параметрами

Маршруты с параметрами требуют проверки правильности разбора параметров params:

test('должен возвращать корректного пользователя', async () => {
  const result = await router.resolve({ pathname: '/users/99' });
  expect(result).toBe('User 99');
});

Важный момент — проверка не только значения параметра, но и его типизации, если приложение использует строгую типизацию или валидацию.

Асинхронные действия маршрутов

Маршруты часто выполняют асинхронные операции, такие как загрузка данных с сервера. Для тестирования необходимо использовать async/await и мокировать внешние вызовы:

const routes = [
  {
    path: '/data/:id',
    async action({ params, context }) {
      const data = await context.fetch(`/api/data/${params.id}`);
      return data;
    }
  }
];

test('должен возвращать данные пользователя', async () => {
  const mockFetch = jest.fn().mockResolvedValue({ name: 'Bob' });
  const result = await new UniversalRouter(routes).resolve({ pathname: '/data/5', context: { fetch: mockFetch } });
  expect(result).toEqual({ name: 'Bob' });
});

Тестирование вложенных маршрутов

Universal Router поддерживает вложенные маршруты через массив children. Тестирование таких маршрутов требует проверки корректного разрешения вложенных путей:

const routes = [
  {
    path: '/dashboard',
    children: [
      { path: '', action: () => 'Dashboard Home' },
      { path: 'stats', action: () => 'Dashboard Stats' }
    ]
  }
];

test('должен возвращать Dashboard Home', async () => {
  const result = await new UniversalRouter(routes).resolve({ pathname: '/dashboard' });
  expect(result).toBe('Dashboard Home');
});

test('должен возвращать Dashboard Stats', async () => {
  const result = await new UniversalRouter(routes).resolve({ pathname: '/dashboard/stats' });
  expect(result).toBe('Dashboard Stats');
});

Проверка редиректов и ошибок

Для маршрутов, использующих редиректы или выбрасывающих ошибки, важно тестировать корректную обработку исключений и возврат значений redirect:

const routes = [
  { path: '/old', action: () => ({ redirect: '/new' }) },
  { path: '/new', action: () => 'New Page' }
];

test('должен редиректить с /old на /new', async () => {
  const result = await new UniversalRouter(routes).resolve({ pathname: '/old' });
  expect(result.redirect).toBe('/new');
});

const errorRoute = [
  { path: '/secret', action: () => { throw new Error('Forbidden'); } }
];

test('должен выбрасывать ошибку для /secret', async () => {
  await expect(new UniversalRouter(errorRoute).resolve({ pathname: '/secret' }))
    .rejects.toThrow('Forbidden');
});

Организация тестов и покрытие

  • Изоляция: каждый тест должен проверять отдельный маршрут или отдельный сценарий.
  • Мокирование контекста: все внешние зависимости следует заменять на мок-объекты.
  • Проверка всех ветвей: для маршрутов с условными переходами или редиректами тестируются все возможные ветви.
  • Асинхронность: асинхронные действия маршрутов проверяются с await и mockResolvedValue/mockRejectedValue.

Практические рекомендации

  • Использовать универсальный метод router.resolve({ pathname, context }) вместо ручной имитации переходов.
  • Создавать отдельный файл с фикстурами контекста для повторного использования.
  • Включать тесты для нестандартных случаев: пустые параметры, неизвестные пути, неверные типы данных.
  • Поддерживать тесты актуальными при изменении логики маршрутов, чтобы избежать регрессий.

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