How to Mock Prop Drilling in React
Testing React components that rely on prop drilling—passing data through multiple layers of intermediate components—can quickly lead to fragile and bloated test suites. This article explains how to efficiently mock prop drilling in React using Jest and React Testing Library. You will learn how to isolate your parent components by mocking nested child components, allowing you to verify component behavior without manually passing props through every intermediate layer.
The Problem with Testing Prop Drilling
Prop drilling occurs when a deeply nested child component requires data from a high-level parent component, forcing all intermediate components to pass the props down. When writing unit tests for the parent component, you are forced to mock or provide valid props for all intermediate and child components. This creates tight coupling between your test and the implementation details of the entire component tree.
Strategy 1: Mocking Intermediate Components
The most direct way to mock prop drilling is to mock the intermediate or leaf components that receive the drilled props. By replacing these components with simple Jest mocks, you stop the propagation of props and isolate the component under test.
Consider a ParentComponent that passes a user profile
down to a DeepChildComponent through several layers:
// ParentComponent.jsx
import React from 'react';
import { IntermediateComponent } from './IntermediateComponent';
export const ParentComponent = ({ user }) => {
return (
<div>
<h1>Admin Dashboard</h1>
<IntermediateComponent user={user} />
</div>
);
};Instead of rendering the actual IntermediateComponent
(which would require rendering everything inside it), you can mock it in
your test file using jest.mock():
// ParentComponent.test.jsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import { ParentComponent } from './ParentComponent';
// Mock the intermediate component to intercept the drilled prop
jest.mock('./IntermediateComponent', () => ({
IntermediateComponent: ({ user }) => (
<div data-testid="mock-intermediate">
Mocked User: {user.name}
</div>
),
}));
describe('ParentComponent', () => {
it('passes the user prop correctly down the tree', () => {
const mockUser = { name: 'Alice' };
render(<ParentComponent user={mockUser} />);
// Assert that the parent successfully passed the prop to the mocked child
expect(screen.getByTestId('mock-intermediate')).toHaveTextContent('Mocked User: Alice');
});
});By mocking IntermediateComponent, you bypass the entire
downstream rendering process, making your test faster and resilient to
changes in deeper components.
Strategy 2: Refactoring to Context to Eliminate Prop Drilling Mocking
If prop drilling makes your components and tests too difficult to maintain, the best solution is often to replace prop drilling with the React Context API. When using Context, you no longer need to pass props through intermediate components.
Instead of mocking prop drilling, you simply wrap your test component in the corresponding Context Provider:
// UserContext.js
import { createContext } from 'react';
export const UserContext = createContext(null);// DeepChildComponent.jsx
import React, { useContext } from 'react';
import { UserContext } from './UserContext';
export const DeepChildComponent = () => {
const user = useContext(UserContext);
return <div>User: {user.name}</div>;
};To test DeepChildComponent or any component wrapping it,
provide the mock state directly via the Provider in your test:
// DeepChildComponent.test.jsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import { DeepChildComponent } from './DeepChildComponent';
import { UserContext } from './UserContext';
describe('DeepChildComponent with Context', () => {
it('consumes user data from Context', () => {
const mockUser = { name: 'Bob' };
render(
<UserContext.Provider value={mockUser}>
<DeepChildComponent />
</UserContext.Provider>
);
expect(screen.getByText('User: Bob')).toBeInTheDocument();
});
});Using this approach eliminates the need to mock individual component imports, resulting in cleaner production code and highly maintainable test files.