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.