E2E тестирование с роутером

Universal Router — это гибкая библиотека маршрутизации для JavaScript, которая позволяет управлять переходами между страницами или состояниями приложения на клиентской и серверной стороне. При написании E2E тестов правильная организация маршрутов позволяет полностью контролировать сценарии пользователя и проверять их поведение без зависимости от внутренней логики приложения.

E2E тестирование с Universal Router строится вокруг двух ключевых принципов: точное определение маршрутов и контроль навигации в тестовой среде. Каждый маршрут определяется объектом с полями path, action, и опционально children. В тестах важно учитывать, что маршруты должны быть детерминированными, чтобы тесты оставались стабильными при изменениях приложения.

import UniversalRouter from 'universal-router';

const routes = [
  { path: '/', action: () => 'Главная' },
  { path: '/login', action: () => 'Страница входа' },
  { path: '/profile/:id', action: ({ params }) => `Профиль пользователя ${params.id}` },
];

const router = new UniversalRouter(routes);

При E2E тестировании маршруты можно вызывать напрямую или через симуляцию навигации в браузере, используя инструменты вроде Cypress или Playwright. Прямой вызов маршрута позволяет проверять корректность сопоставления URL и действия, не затрагивая DOM. Например:

const result = await router.resolve({ pathname: '/profile/42' });
console.assert(result === 'Профиль пользователя 42');

Настройка маршрутов для тестов

Для эффективного тестирования рекомендуется выделять тестовые маршруты, которые могут использовать фиктивные данные, мок-сервисы или специальные компоненты. Это позволяет изолировать тесты от реальной API и ускоряет их выполнение.

const testRoutes = [
  {
    path: '/dashboard',
    action: () => ({ title: 'Тестовая панель', data: [] })
  },
  {
    path: '/settings',
    action: () => ({ title: 'Настройки', editable: true })
  }
];

const testRouter = new UniversalRouter(testRoutes);

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

Навигация и асинхронные действия

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

const asyncRoutes = [
  {
    path: '/products/:id',
    action: async ({ params }) => {
      const response = await fetch(`/api/products/${params.id}`);
      const data = await response.json();
      return data.name;
    }
  }
];

const asyncRouter = new UniversalRouter(asyncRoutes);

Тесты должны использовать мокирование сетевых запросов или встроенные промисы, чтобы контролировать результаты и проверять правильность обработки асинхронных данных:

const mockFetch = jest.fn(() =>
  Promise.resolve({ json: () => Promise.resolve({ name: 'Товар 1' }) })
);

global.fetch = mockFetch;

const result = await asyncRouter.resolve({ pathname: '/products/1' });
console.assert(result === 'Товар 1');

Работа с параметрами маршрута

Параметры, передаваемые через URL, играют ключевую роль в тестах. Universal Router автоматически извлекает их в объект params, что позволяет писать тесты, проверяющие динамическую генерацию контента:

const userRoutes = [
  { path: '/user/:userId', action: ({ params }) => `Пользователь ${params.userId}` }
];

const userRouter = new UniversalRouter(userRoutes);
const output = await userRouter.resolve({ pathname: '/user/99' });
console.assert(output === 'Пользователь 99');

В E2E тестах рекомендуется проверять как валидные, так и невалидные параметры, чтобы убедиться, что маршрутизация корректно обрабатывает ошибки:

try {
  await userRouter.resolve({ pathname: '/user/' });
} catch (err) {
  console.assert(err.message.includes('No route matched'));
}

Рекурсивные маршруты и вложенные пути

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

const nestedRoutes = [
  {
    path: '/admin',
    action: () => 'Админ панель',
    children: [
      { path: '/users', action: () => 'Список пользователей' },
      { path: '/settings', action: () => 'Настройки админа' }
    ]
  }
];

const nestedRouter = new UniversalRouter(nestedRoutes);

const usersPage = await nestedRouter.resolve({ pathname: '/admin/users' });
console.assert(usersPage === 'Список пользователей');

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

Интеграция с браузерной навигацией

Для тестов, которые проверяют работу приложения в реальном браузере, Universal Router может быть интегрирован с History API или Hash-based навигацией. E2E инструменты, такие как Playwright или Cypress, позволяют симулировать клики по ссылкам и проверять вызовы router.resolve:

await page.goto('/login');
const content = await page.textContent('h1');
console.assert(content === 'Страница входа');

Дополнительно можно тестировать редиректы и fallback маршруты, что критично для проверки пользовательского опыта при неправильных URL.

const fallbackRoutes = [
  { path: '/', action: () => 'Главная' },
  { path: '(.*)', action: () => '404 Страница не найдена' }
];

const fallbackRouter = new UniversalRouter(fallbackRoutes);
const notFound = await fallbackRouter.resolve({ pathname: '/unknown' });
console.assert(notFound === '404 Страница не найдена');

Логирование и отладка в тестах

Universal Router предоставляет возможность логировать вызовы маршрутов через middleware-подобные обертки. Это помогает анализировать поведение приложения в E2E тестах:

async function loggingResolver(context, next) {
  console.log('Переход к', context.pathname);
  const result = await next();
  console.log('Результат:', result);
  return result;
}

const loggedRouter = new UniversalRouter(routes, { resolveRoute: loggingResolver });

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

Совмещение с моками и фиктивными данными

Для изоляции тестов можно создавать специальные маршруты с моковыми действиями. Это позволяет проверить интерфейс и навигацию без зависимости от реальной базы данных или внешних API:

const mockRoutes = [
  { path: '/mock-data', action: () => ({ items: [1, 2, 3] }) }
];

const mockRouter = new UniversalRouter(mockRoutes);
const data = await mockRouter.resolve({ pathname: '/mock-data' });
console.assert(data.items.length === 3);

Использование таких маршрутов ускоряет тесты и делает их предсказуемыми.