useNavigate hook тестирование

useNavigate — это хук, предоставляемый библиотекой react-router-dom, который позволяет программно изменять маршрут в приложении. Его основная цель — это навигация между страницами, без необходимости взаимодействовать с UI напрямую. В процессе тестирования React-компонентов важно научиться правильно проверять работу маршрутизации, особенно когда приложение использует useNavigate для смены маршрутов.

Принципы тестирования useNavigate

Для эффективного тестирования компонентов, использующих хук useNavigate, необходимо понять, как правильно использовать и симулировать его действия. Обычно это делается с помощью имитации навигации, проверки правильности вызова функции и определения того, как компонент реагирует на изменения маршрута.

Мокинг хука useNavigate

Поскольку useNavigate является хуком, предоставляемым react-router-dom, он не может быть напрямую использован или проверен в тестах без мокирования. В стандартных тестах, где не используется реальный роутер, его нужно замокать, чтобы можно было отслеживать вызовы навигации и тестировать их в контексте конкретного компонента.

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

// Мокаем хук useNavigate
jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useNavigate: jest.fn(),
}));

test('проверка навигации при клике на кнопку', () => {
  const navigate = jest.fn(); // Мокаем функцию navigate
  require('react-router-dom').useNavigate.mockReturnValue(navigate);

  const { getByText } = render(
    <MemoryRouter>
      <MyComponent />
    </MemoryRouter>
  );

  fireEvent.click(getByText(/Перейти/)); // Имитируем клик по кнопке
  expect(navigate).toHaveBeenCalledWith('/новый-маршрут'); // Проверяем, что navigate был вызван с правильным маршрутом
});

В этом примере jest.mock используется для замокания хука useNavigate. В тесте мы создаем мок-функцию navigate, которую передаем в компонент через мокированный хук. Это позволяет проверить, была ли навигация выполнена правильно при взаимодействии с компонентом.

Использование MemoryRouter для тестирования навигации

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

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

test('проверка смены маршрута', () => {
  const { getByText } = render(
    <MemoryRouter initialEntries={['/']}>
      <Routes>
        <Route path="/" element={<MyComponent />} />
        <Route path="/новый-маршрут" element={<div>Новый маршрут</div>} />
      </Routes>
    </MemoryRouter>
  );

  fireEvent.click(getByText(/Перейти/)); // Имитируем клик по кнопке
  expect(getByText(/Новый маршрут/)).toBeInTheDocument(); // Проверяем, что новый маршрут отобразился
});

В этом тесте используется MemoryRouter с маршрутом /. После клика по кнопке, которая вызывает навигацию, проверяется, что контент компонента с новым маршрутом появился на экране.

Проверка переходов между страницами

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

test('проверка изменения маршрута при навигации', () => {
  const { getByText } = render(
    <MemoryRouter initialEntries={['/']}>
      <Routes>
        <Route path="/" element={<MyComponent />} />
        <Route path="/новый-маршрут" element={<div>Новый маршрут</div>} />
      </Routes>
    </MemoryRouter>
  );

  fireEvent.click(getByText(/Перейти/)); // Имитируем клик по кнопке
  expect(window.location.pathname).toBe('/новый-маршрут'); // Проверяем, что путь изменился
});

В данном случае тест проверяет, что после нажатия на кнопку Перейти путь в браузере изменится на /новый-маршрут.

Тестирование с асинхронными действиями

Если компонент, использующий хук useNavigate, вызывает навигацию после выполнения асинхронной операции (например, после загрузки данных), то для теста потребуется использовать асинхронные методы, такие как waitFor или findBy.

import { render, fireEvent, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import MyComponent from './MyComponent';

test('проверка навигации после асинхронной операции', async () => {
  const navigate = jest.fn();
  require('react-router-dom').useNavigate.mockReturnValue(navigate);

  const { getByText } = render(
    <MemoryRouter>
      <MyComponent />
    </MemoryRouter>
  );

  fireEvent.click(getByText(/Загрузить и перейти/)); // Имитируем клик по кнопке

  await waitFor(() => expect(navigate).toHaveBeenCalledWith('/новый-маршрут')); // Ожидаем, что навигация произойдет после асинхронной операции
});

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

Мокирование useNavigate в более сложных компонентах

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

import { render, fireEvent } from '@testing-library/react';
import { MyContextProvider } from './MyContext';
import { useNavigate } from 'react-router-dom';
import MyComponent from './MyComponent';

jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useNavigate: jest.fn(),
}));

test('проверка навигации внутри контекста', () => {
  const navigate = jest.fn();
  useNavigate.mockReturnValue(navigate);

  const { getByText } = render(
    <MyContextProvider>
      <MyComponent />
    </MyContextProvider>
  );

  fireEvent.click(getByText(/Перейти с контекстом/));
  expect(navigate).toHaveBeenCalledWith('/новый-маршрут');
});

В этом примере компонент, использующий useNavigate, оборачивается в провайдер контекста, что добавляет дополнительную сложность. Мокирование хука useNavigate позволяет проверить его работу, даже если компонент зависит от контекста.

Итоги

Тестирование компонентов, использующих useNavigate, требует правильного мокирования самого хука и использования обертки маршрутизатора, такой как MemoryRouter, для эмуляции работы с маршрутами. Важно понимать, что для разных типов взаимодействий (клики, асинхронные действия, изменения контекста) могут потребоваться различные подходы для обеспечения точности тестирования.