Для тестирования компонентов, использующих React Router, необходимо корректно настроить окружение. Основной инструмент — React Testing Library (RTL), который работает совместно с Jest. Для корректного рендеринга маршрутов требуется обернуть тестируемый компонент в провайдер маршрутизатора:
import { render } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import App from './App';
test('рендеринг маршрута /home', () => {
const { getByText } = render(
<MemoryRouter initialEntries={['/home']}>
<App />
</MemoryRouter>
);
expect(getByText('Главная страница')).toBeInTheDocument();
});
MemoryRouter — ключевой элемент для тестирования, так как он позволяет управлять историей маршрутов без воздействия на браузер.
Для проверки навигации между страницами важно использовать
fireEvent или
userEvent для имитации действий
пользователя. Пример проверки перехода по ссылке:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter } from 'react-router-dom';
import App from './App';
test('переход на страницу About', async () => {
render(
<MemoryRouter initialEntries={['/']}>
<App />
</MemoryRouter>
);
const link = screen.getByText('О нас');
await userEvent.click(link);
expect(screen.getByText('Страница о компании')).toBeInTheDocument();
});
Ключевой момент: MemoryRouter принимает
массив initialEntries, что позволяет запускать тест с
конкретного маршрута, а userEvent симулирует реальные
пользовательские взаимодействия с элементами.
Для динамических маршрутов с параметрами важно проверять правильное
получение этих параметров через useParams.
import { render, screen } from '@testing-library/react';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
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();
});
Выделение ключевого: для маршрутов с параметрами
важно использовать <Routes> и
<Route> внутри теста, иначе useParams
вернёт пустой объект.
Редиректы проверяются через рендер с определённого маршрута и последующую проверку появления ожидаемого компонента.
import { render, screen } from '@testing-library/react';
import { MemoryRouter, Routes, Route, Navigate } from 'react-router-dom';
test('редирект с /login на /dashboard', () => {
render(
<MemoryRouter initialEntries={['/login']}>
<Routes>
<Route path="/login" element={<Navigate to="/dashboard" />} />
<Route path="/dashboard" element={<div>Панель управления</div>} />
</Routes>
</MemoryRouter>
);
expect(screen.getByText('Панель управления')).toBeInTheDocument();
});
Особенность: редиректы в тестах необходимо проверять
через компонент Navigate, так как обычный
<Redirect> используется только в старых версиях React
Router.
React Router хранит историю маршрутов, которая доступна через
useNavigate и useLocation. Для тестирования
этих хуков можно использовать MemoryRouter с отслеживанием
истории:
import { render, screen } from '@testing-library/react';
import { MemoryRouter, Routes, Route, useNavigate } from 'react-router-dom';
import userEvent from '@testing-library/user-event';
function TestComponent() {
const navigate = useNavigate();
return <button onCl ick={() => navigate('/next')}>Далее</button>;
}
test('навигация с помощью useNavigate', async () => {
render(
<MemoryRouter initialEntries={['/start']}>
<Routes>
<Route path="/start" element={<TestComponent />} />
<Route path="/next" element={<div>Следующая страница</div>} />
</Routes>
</MemoryRouter>
);
await userEvent.click(screen.getByText('Далее'));
expect(screen.getByText('Следующая страница')).toBeInTheDocument();
});
Ключевой момент: тестирование программной навигации
через useNavigate позволяет проверять функционал без кликов
по ссылкам.
При использовании маршрутов с асинхронной подгрузкой данных или
ленивой загрузкой компонентов следует применять waitFor или
findBy... для ожидания появления элементов:
import { render, screen } from '@testing-library/react';
import { MemoryRouter, Routes, Route } from 'react-router-dom';
import userEvent from '@testing-library/user-event';
import React, { Suspense, lazy } from 'react';
const AboutPage = lazy(() => import('./AboutPage'));
test('ленивая загрузка AboutPage', async () => {
render(
<MemoryRouter initialEntries={['/about']}>
<Suspense fallback={<div>Загрузка...</div>}>
<Routes>
<Route path="/about" element={<AboutPage />} />
</Routes>
</Suspense>
</MemoryRouter>
);
expect(await screen.findByText('О компании')).toBeInTheDocument();
});
Выделение ключевого: Suspense и
findBy... позволяют корректно обрабатывать асинхронные
операции, избегая ложных отрицательных результатов в тестах.
Для маршрутов, доступ к которым ограничен авторизацией, тесты должны симулировать состояние пользователя:
import { render, screen } from '@testing-library/react';
import { MemoryRouter, Routes, Route, Navigate } from 'react-router-dom';
function ProtectedRoute({ isAuth, children }) {
return isAuth ? children : <Navigate to="/login" />;
}
test('доступ к защищённой странице при неавторизованном пользователе', () => {
render(
<MemoryRouter initialEntries={['/dashboard']}>
<Routes>
<Route path="/dashboard" element={<ProtectedRoute isAuth={false}><div>Панель</div></ProtectedRoute>} />
<Route path="/login" element={<div>Страница входа</div>} />
</Routes>
</MemoryRouter>
);
expect(screen.getByText('Страница входа')).toBeInTheDocument();
});
Особенность: тестирование защищённых маршрутов помогает убедиться, что редирект выполняется корректно при различных состояниях авторизации.
Эта методология обеспечивает полное покрытие тестами компонентов с React Router, включая навигацию, редиректы, динамические параметры, асинхронные загрузки и защиту маршрутов.