Mock React Router Components in Testing

Testing React applications often requires isolating components from their routing context to prevent external dependencies from breaking unit tests. This article provides a straightforward guide on how to mock route components, navigation hooks, and router contexts in React using Jest and React Testing Library. You will learn the practical code patterns needed to mock react-router-dom hooks like useNavigate and useParams, as well as how to mock entire route-rendered components.

Why Mock Route Components?

When unit testing a component that interacts with React Router, rendering the actual router can introduce unnecessary complexity. Mocking allows you to: * Isolate behavior: Test the component’s internal logic without worrying about path matching or navigation state. * Control outputs: Mock return values from hooks like useParams to test different scenarios (e.g., viewing a specific user ID). * Assert side effects: Verify if navigation functions like navigate() were called with the correct arguments.


Method 1: Mocking react-router-dom Hooks

The most common requirement is mocking hooks like useNavigate, useParams, or useLocation. You can mock the entire react-router-dom module using Jest.

Here is how to mock useNavigate to assert that a user is redirected on a button click:

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

// 1. Create a mock navigate function
const mockNavigate = jest.fn();

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

test('navigates to dashboard on button click', () => {
  render(<MyComponent />);
  
  const button = screen.getByRole('button', { name: /go to dashboard/i });
  fireEvent.click(button);
  
  // 3. Assert that the navigate function was called with the correct path
  expect(mockNavigate).toHaveBeenCalledWith('/dashboard');
});

To mock useParams to simulate a specific URL parameter (like an ID):

jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useParams: () => ({ userId: '12345' }),
}));

Method 2: Mocking a Custom Route Component

If your component renders a child component that relies heavily on a route context, you can mock that child component entirely to bypass the router requirement.

Suppose you have a Dashboard component that renders a complex UserProfileRoute component. You can mock the child component in your test file:

import { render, screen } from '@testing-library/react';
import Dashboard from './Dashboard';

// Mock the child component to render a simple placeholder
jest.mock('./UserProfileRoute', () => {
  return function MockUserProfile() {
    return <div data-testid="mock-profile">Mocked User Profile Route</div>;
  };
});

test('renders dashboard with mocked profile route', () => {
  render(<Dashboard />);
  
  expect(screen.getByTestId('mock-profile')).toBeInTheDocument();
  expect(screen.getByText('Mocked User Profile Route')).toBeInTheDocument();
});

Method 3: Using MemoryRouter (Alternative to Mocking)

If you prefer not to mock the router hooks or components, you can wrap your component in MemoryRouter from react-router-dom. This is the recommended approach for integration tests where you want to test actual routing behavior.

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

test('renders correct content based on route', () => {
  render(
    <MemoryRouter initialEntries={['/user/99']}>
      <Routes>
        <Route path="/user/:id" element={<MyComponent />} />
      </Routes>
    </MemoryRouter>
  );

  // Assert against the rendered output of MyComponent with parameter id = 99
  expect(screen.getByText('User ID: 99')).toBeInTheDocument();
});