How to Mock Outlet in React Router

Testing nested routes in React applications often requires mocking the Outlet component from React Router. This article provides a straightforward guide on how to mock the Outlet component using Jest and React Testing Library, allowing you to isolate and unit test your layout components without rendering the actual child routes.

Why Mock the Outlet Component?

In React Router, the <Outlet /> component is used in parent route elements to render their child route elements. When writing unit tests for a layout component (like a sidebar or a header wrapper), you want to test the layout’s behavior in isolation.

Mocking <Outlet /> allows you to replace the dynamic child components with a simple placeholder. This ensures your tests are fast, predictable, and not dependent on the implementation details of child routes.

Step-by-Step Guide to Mocking Outlet

The most efficient way to mock the Outlet component is by using jest.mock() to spy on the react-router-dom library.

1. The Component Under Test

Suppose you have a simple layout component called DashboardLayout that includes a header and renders child routes using <Outlet />:

// DashboardLayout.jsx
import React from 'react';
import { Outlet } from 'react-router-dom';

export default function DashboardLayout() {
  return (
    <div className="dashboard">
      <header>
        <h1>Dashboard Header</h1>
      </header>
      <main>
        <Outlet />
      </main>
    </div>
  );
}

2. Creating the Test with Jest

In your test file, mock the react-router-dom module. Keep the actual functionality of the rest of the library using jest.requireActual, but override the Outlet component to render a dummy element with a specific data-testid.

// DashboardLayout.test.jsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import DashboardLayout from './DashboardLayout';

// Mock react-router-dom, specifically overriding Outlet
jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  Outlet: () => <div data-testid="mock-outlet">Mocked Child Content</div>,
}));

describe('DashboardLayout Component', () => {
  it('renders the header and the mocked outlet placeholder', () => {
    render(<DashboardLayout />);

    // Assert that the layout header is rendered
    const headerElement = screen.getByText('Dashboard Header');
    expect(headerElement).toBeInTheDocument();

    // Assert that the mocked Outlet is rendered in place of child routes
    const outletElement = screen.getByTestId('mock-outlet');
    expect(outletElement).toBeInTheDocument();
    expect(outletElement).toHaveTextContent('Mocked Child Content');
  });
});

Alternative: Testing with MemoryRouter

If you prefer integration testing over mocking, you can avoid mocking Outlet entirely by wrapping your component in a MemoryRouter and defining a test route.

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

describe('DashboardLayout with MemoryRouter', () => {
  it('renders child routes correctly', () => {
    render(
      <MemoryRouter initialEntries={['/dashboard/settings']}>
        <Routes>
          <Route path="/dashboard" element={<DashboardLayout />}>
            <Route path="settings" element={<div>Settings Page</div>} />
          </Route>
        </Routes>
      </MemoryRouter>
    );

    expect(screen.getByText('Dashboard Header')).toBeInTheDocument();
    expect(screen.getByText('Settings Page')).toBeInTheDocument();
  });
});

Mocking using jest.mock is ideal for pure layout unit tests, while the MemoryRouter approach is best suited for integration testing complete routing behaviors.