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();
});