При тестировании React-компонентов, использующих маршрутизацию через
react-router-dom, возникает необходимость симулировать
поведение маршрутизатора, чтобы компоненты корректно реагировали на
изменение URL и навигацию. Простое использование render из
React Testing Library без обертки маршрутизатора часто приводит к
ошибкам, так как компоненты ожидают контекст роутера.
MemoryRouter для тестовMemoryRouter — это легковесный маршрутизатор,
предназначенный для тестирования. Он хранит историю навигации в памяти и
позволяет управлять начальными маршрутами:
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import App from './App';
test('рендеринг компонента на определённом маршруте', () => {
render(
<MemoryRouter initialEntries={['/dashboard']}>
<App />
</MemoryRouter>
);
expect(screen.getByText(/Dashboard/i)).toBeInTheDocument();
});
Ключевые моменты:
initialEntries задаёт путь, на котором компонент будет
изначально отрендерен.useLocation,
useParams или useNavigate, корректно работают
внутри MemoryRouter.Для проверки переходов между страницами можно манипулировать историей
маршрутов. В MemoryRouter история доступна через
initialEntries и initialIndex:
import { createMemoryHistory } from 'history';
import { Router } from 'react-router-dom';
import userEvent from '@testing-library/user-event';
test('навигация по ссылкам', async () => {
const history = createMemoryHistory({ initialEntries: ['/'] });
render(
<Router location={history.location} navigator={history}>
<App />
</Router>
);
const link = screen.getByText(/About/i);
await userEvent.click(link);
expect(history.location.pathname).toBe('/about');
expect(screen.getByText(/About Page/i)).toBeInTheDocument();
});
Особенности подхода:
Router, который принимает объект
history, созданный вручную.Компоненты с параметрами маршрута (/users/:id) требуют
проверки правильной работы useParams. Для этого достаточно
передать путь с конкретным значением:
test('рендеринг страницы пользователя с параметром id', () => {
render(
<MemoryRouter initialEntries={['/users/42']}>
<App />
</MemoryRouter>
);
expect(screen.getByText(/User ID: 42/i)).toBeInTheDocument();
});
Выводы по динамическим маршрутам:
MemoryRouter корректно парсит параметры и предоставляет
их через useParams.При тестировании компонентов, вызывающих navigate после
событий или эффектов, удобно использовать мок-функции:
import { useNavigate } from 'react-router-dom';
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: jest.fn(),
}));
test('навигация после сабмита формы', () => {
const mockNavigate = jest.fn();
useNavigate.mockReturnValue(mockNavigate);
render(<FormComponent />);
userEvent.click(screen.getByText(/Submit/i));
expect(mockNavigate).toHaveBeenCalledWith('/success');
});
Принципы мокирования:
useNavigate можно заменить на функцию-шпион, чтобы
проверить, что переход был вызван с правильным маршрутом.MemoryRouter, если цель — только проверить вызов
навигации.Для сложных компонентов, где маршруты зависят от состояния Redux или контекста, рекомендуется оборачивать компоненты в несколько провайдеров:
import { Provider } from 'react-redux';
import { store } from './store';
render(
<Provider store={store}>
<MemoryRouter initialEntries={['/profile']}>
<App />
</MemoryRouter>
</Provider>
);
Замечания:
Компоненты часто загружают данные после рендеринга. Для асинхронной
навигации применяются утилиты findBy:
test('отображение данных после перехода', async () => {
render(
<MemoryRouter initialEntries={['/dashboard']}>
<App />
</MemoryRouter>
);
const userGreeting = await screen.findByText(/Welcome, John/i);
expect(userGreeting).toBeInTheDocument();
});
Особенности асинхронного подхода:
findBy автоматически ждёт появления элемента в DOM, что
идеально для данных, загружаемых через эффекты.userEvent для имитации навигации
и проверки реакций компонентов на изменения состояния.MemoryRouter для базового рендеринга.initialEntries для задания начального
пути.history через
Router.useParams.useNavigate для проверки вызовов
навигации.findBy*) для
компонентов с загрузкой данных.Такой подход обеспечивает полное тестирование компонентов, зависящих от маршрутов, и позволяет имитировать как пользовательскую навигацию, так и условное отображение компонентов на разных путях.