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

React Router предоставляет мощный механизм маршрутизации для одностраничных приложений (SPA) на React, позволяя управлять переходами между страницами без полной перезагрузки браузера. Интеграционное тестирование навигации в таких приложениях требует внимания к нескольким ключевым аспектам: правильной конфигурации маршрутов, корректной работе компонентов при изменении URL и корректной обработке переходов между маршрутами.

Настройка тестовой среды

Для интеграционного тестирования React Router обычно используется комбинация React Testing Library и Jest. Важный элемент — обертка тестируемого компонента в <MemoryRouter> или <BrowserRouter> для имитации поведения маршрутизатора в среде тестов:

import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import App from './App';

test('рендерит домашнюю страницу по умолчанию', () => {
  render(
    <MemoryRouter initialEntries={['/']}>
      <App />
    </MemoryRouter>
  );
  expect(screen.getByText('Главная')).toBeInTheDocument();
});

Ключевой момент: initialEntries позволяет задать начальный путь для маршрутизатора в тестах, что критично для проверки поведения навигации на разных страницах.

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

Навигация в React Router может осуществляться через компонент <Link> или через хук useNavigate. Тесты должны учитывать оба способа:

import { fireEvent } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import App from './App';

test('переход на страницу о приложении', () => {
  render(
    <MemoryRouter initialEntries={['/']}>
      <App />
    </MemoryRouter>
  );

  const aboutLink = screen.getByText('О приложении');
  fireEvent.click(aboutLink);

  expect(screen.getByText('Информация о приложении')).toBeInTheDocument();
});

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

Тестирование динамических маршрутов и параметров URL

React Router позволяет использовать динамические сегменты пути (:id, :slug), которые необходимо проверять на корректность обработки в компонентах:

import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { render, screen } from '@testing-library/react';
import UserPage from './UserPage';

test('рендерит страницу пользователя с динамическим ID', () => {
  render(
    <MemoryRouter initialEntries={['/user/42']}>
      <Routes>
        <Route path="/user/:id" element={<UserPage />} />
      </Routes>
    </MemoryRouter>
  );

  expect(screen.getByText('Пользователь 42')).toBeInTheDocument();
});

Ключевой момент: использование initialEntries в сочетании с маршрутом, включающим параметры, позволяет тестировать правильную интерпретацию URL и передачу параметров в компонент.

Проверка редиректов и защищённых маршрутов

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

import { MemoryRouter, Routes, Route } from 'react-router-dom';
import { render, screen } from '@testing-library/react';
import App from './App';

test('незалогиненный пользователь перенаправляется на страницу входа', () => {
  render(
    <MemoryRouter initialEntries={['/profile']}>
      <App />
    </MemoryRouter>
  );

  expect(screen.getByText('Вход')).toBeInTheDocument();
});

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

Эмуляция навигации с помощью useNavigate

Хуки React Router требуют отдельного подхода при тестировании:

import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import ComponentWithNavigate from './ComponentWithNavigate';
import userEvent from '@testing-library/user-event';

test('навигация через useNavigate работает корректно', async () => {
  render(
    <MemoryRouter initialEntries={['/start']}>
      <ComponentWithNavigate />
    </MemoryRouter>
  );

  const button = screen.getByText('Перейти далее');
  await userEvent.click(button);

  expect(screen.getByText('Следующая страница')).toBeInTheDocument();
});

Ключевой момент: использование userEvent вместо fireEvent более точно имитирует реальные действия пользователя, включая асинхронные переходы.

Стратегии комплексного тестирования маршрутов

  1. Тестирование всех маршрутов: каждый путь приложения должен быть покрыт интеграционным тестом, включая ошибочные и редиректные маршруты.
  2. Проверка состояния компонентов: при переходе между маршрутами важно проверять сохранение или сброс состояния, особенно при динамических данных.
  3. Параллельная проверка UI и URL: тест должен удостоверяться, что содержимое страницы соответствует URL, а адресная строка обновляется корректно.
  4. Использование MemoryRouter для изоляции тестов: это предотвращает влияние браузера на результаты и обеспечивает предсказуемое поведение тестов.

Инструменты для расширенного тестирования

  • React Testing Library: основной инструмент для рендеринга компонентов и взаимодействия с DOM.
  • Jest: среда выполнения тестов, обеспечивающая мок-функции и асинхронные проверки.
  • MSW (Mock Service Worker): используется для имитации сетевых запросов при навигации на страницы с данными с сервера.
  • React Router DevTools (для локальной проверки): полезен для визуального контроля работы маршрутов при разработке и тестировании.

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

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

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