В React-приложениях с использованием маршрутизации (React Router, Next.js или других библиотек) тестирование компонентов, зависящих от навигации, требует особого подхода. Важно проверять не только рендеринг компонентов, но и корректное изменение URL, переходы между страницами и реакцию интерфейса на маршруты.
Для тестирования навигации необходимо обернуть компоненты в провайдер
маршрутизатора. В случае React Router используется
MemoryRouter, который позволяет управлять историей
переходов в тестовой среде.
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import App from './App';
render(
<MemoryRouter initialEntries={['/']}>
<App />
</MemoryRouter>
);
Ключевые моменты:
initialEntries задаёт начальный путь, с которого
начинается тест.MemoryRouter не взаимодействует с настоящим URL
браузера, что делает тесты изолированными.history или
библиотеку user-event для симуляции кликов по ссылкам.Для эмуляции кликов по ссылкам используется библиотека
@testing-library/user-event. Она позволяет воспроизводить
пользовательские действия максимально близко к реальному поведению
браузера.
import userEvent from '@testing-library/user-event';
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import App from './App';
render(
<MemoryRouter initialEntries={['/']}>
<App />
</MemoryRouter>
);
userEvent.click(screen.getByText('О нас'));
expect(screen.getByText('Страница о компании')).toBeInTheDocument();
Особенности тестирования:
useNavigate и программных переходовКомпоненты могут использовать useNavigate для
программной навигации. Для тестирования таких переходов важно проверять
конечное состояние приложения после вызова функции.
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import userEvent from '@testing-library/user-event';
import NavigationButton from './NavigationButton';
render(
<MemoryRouter initialEntries={['/']}>
<NavigationButton />
</MemoryRouter>
);
userEvent.click(screen.getByRole('button', { name: /Перейти/i }));
expect(screen.getByText('Целевая страница')).toBeInTheDocument();
Подчёркивается:
history.Для компонентов, защищённых авторизацией, часто используется редирект. Проверка требует моделирования состояния пользователя.
import { render, screen } from '@testing-library/react';
import { MemoryRouter, Routes, Route } from 'react-router-dom';
import ProtectedPage from './ProtectedPage';
import LoginPage from './LoginPage';
const isAuthenticated = false;
render(
<MemoryRouter initialEntries={['/protected']}>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/protected" element={isAuthenticated ? <ProtectedPage /> : <LoginPage />} />
</Routes>
</MemoryRouter>
);
expect(screen.getByText('Страница входа')).toBeInTheDocument();
Ключевые моменты:
MemoryRouter позволяет отслеживать историю переходов для
более сложных сценариев.
import { render, screen } from '@testing-library/react';
import { MemoryRouter, Routes, Route } from 'react-router-dom';
import userEvent from '@testing-library/user-event';
import App from './App';
const historyEntries = ['/home'];
render(
<MemoryRouter initialEntries={historyEntries}>
<App />
</MemoryRouter>
);
userEvent.click(screen.getByText('Профиль'));
expect(screen.getByText('Страница профиля')).toBeInTheDocument();
Особенности:
initialEntries задаёт стек истории.initialIndex и последовательно вызывая
userEvent.click.React Router поддерживает маршруты с параметрами
(/user/:id). Тестирование таких страниц требует передачи
конкретных значений параметров в initialEntries.
import { render, screen } from '@testing-library/react';
import { MemoryRouter, Routes, Route } from 'react-router-dom';
import UserPage from './UserPage';
render(
<MemoryRouter initialEntries={['/user/42']}>
<Routes>
<Route path="/user/:id" element={<UserPage />} />
</Routes>
</MemoryRouter>
);
expect(screen.getByText('Пользователь 42')).toBeInTheDocument();
Важно учитывать:
window.location.search или библиотеку
query-string.Некоторые страницы могут загружать данные после навигации. В этом
случае используется findBy* из React Testing Library, чтобы
дождаться появления элементов.
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import App from './App';
import userEvent from '@testing-library/user-event';
render(
<MemoryRouter initialEntries={['/']}>
<App />
</MemoryRouter>
);
userEvent.click(screen.getByText('Новости'));
const article = await screen.findByText('Главная новость');
expect(article).toBeInTheDocument();
Особенности:
findBy* автоматически ждёт появления элемента до
таймаута.MemoryRouter вместо
BrowserRouter для изоляции тестов.user-event вместо прямого вызова функций
маршрутизатора.Эти подходы позволяют создавать надёжные тесты React-приложений с навигацией, обеспечивая проверку функциональности маршрутов без зависимости от внешней среды браузера.