How to Test useNavigate Hook in React

Testing the useNavigate hook from React Router is essential for ensuring your application navigates to the correct routes upon user actions. This article provides a straightforward, step-by-step guide on how to mock and assert the useNavigate hook using Jest and React Testing Library, ensuring your routing logic is robust and error-free.

Step 1: Create the Component with useNavigate

Before writing the test, let’s look at a typical React component that uses the useNavigate hook to redirect a user when a button is clicked.

import React from 'react';
import { useNavigate } from 'react-router-dom';

export function NavigationButton() {
  const navigate = useNavigate();

  return (
    <button onClick={() => navigate('/dashboard')}>
      Go to Dashboard
    </button>
  );
}

Step 2: Mock react-router-dom in Your Test File

To test if useNavigate is called correctly, you need to mock the react-router-dom library. By creating a mock function (spy) for navigate, you can spy on its calls and assert whether the component triggered the navigation properly.

Place this mock at the top of your test file, outside of any describe or test blocks:

import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { NavigationButton } from './NavigationButton';

// Create a mock function to track navigate calls
const mockNavigate = jest.fn();

// Mock react-router-dom and override useNavigate
jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useNavigate: () => mockNavigate,
}));

Step 3: Write the Assertion Test

Now, write the test case. You will render the component, simulate a click event on the button using @testing-library/user-event, and assert that mockNavigate was called with the expected route.

describe('NavigationButton Component', () => {
  beforeEach(() => {
    // Clear mock call history before each test
    mockNavigate.mockClear();
  });

  it('navigates to the dashboard when the button is clicked', async () => {
    render(<NavigationButton />);

    const button = screen.getByRole('button', { name: /go to dashboard/i });
    
    // Simulate user click
    await userEvent.click(button);

    // Verify navigate was called with the correct path
    expect(mockNavigate).toHaveBeenCalledTimes(1);
    expect(mockNavigate).toHaveBeenCalledWith('/dashboard');
  });
});

Alternative Approach: Using MemoryRouter

If you prefer integration testing over mocking hooks directly, you can wrap your component in a MemoryRouter from react-router-dom. This allows you to test route transitions dynamically without manual mocking.

import { MemoryRouter, Routes, Route } from 'react-router-dom';

it('renders the dashboard page after button click', async () => {
  render(
    <MemoryRouter initialEntries={['/']}>
      <Routes>
        <Route path="/" element={<NavigationButton />} />
        <Route path="/dashboard" element={<div>Dashboard Page</div>} />
      </Routes>
    </MemoryRouter>
  );

  const button = screen.getByRole('button', { name: /go to dashboard/i });
  await userEvent.click(button);

  // Assert that the navigation completed and target UI rendered
  expect(screen.getByText('Dashboard Page')).toBeInTheDocument();
});