How to Mock Code Splitting in React
Testing React applications that utilize code splitting via
React.lazy and Suspense can introduce
complexity because components load asynchronously. This article explains
how to mock code-split components in Jest and React Testing Library,
allowing you to write reliable, synchronous unit tests without waiting
for real network chunks to load.
Mocking the Dynamically Imported Component
The most straightforward way to mock code splitting is to mock the
module of the component being dynamically imported. By intercepting the
import path, you bypass the asynchronous behavior of
React.lazy altogether.
If you have a component configured like this:
import React, { Suspense } from 'react';
const LazyProfile = React.lazy(() => import('./components/Profile'));
export default function App() {
return (
<div>
<h1>User Dashboard</h1>
<Suspense fallback={<div>Loading...</div>}>
<LazyProfile />
</Suspense>
</div>
);
}You can mock Profile in your Jest test file so that
React renders a simple placeholder component instantly:
import { render, screen } from '@testing-library/react';
import App from './App';
// Mock the dynamically imported component
jest.mock('./components/Profile', () => {
return function DummyProfile() {
return <div data-testid="mocked-profile">Mocked Profile Component</div>;
};
});
test('renders the dashboard and mocked profile', () => {
render(<App />);
expect(screen.getByText('User Dashboard')).toBeInTheDocument();
expect(screen.getByTestId('mocked-profile')).toBeInTheDocument();
});Using this approach, you do not need to wrap your test in asynchronous helpers, because the mocked component resolves immediately during the render cycle.
Mocking React.lazy Globally
If you prefer to resolve all lazy-loaded components synchronously
across your test suite without mocking individual file paths, you can
mock React.lazy globally. Add this configuration to your
Jest setup file:
jest.mock('react', () => {
const originalReact = jest.requireActual('react');
return {
...originalReact,
lazy: (importFunc) => {
const LazyComponent = (props) => {
const [Component, setComponent] = originalReact.useState(null);
originalReact.useEffect(() => {
importFunc().then((module) => {
setComponent(() => module.default);
});
}, []);
return Component ? <Component {...props} /> : null;
};
return LazyComponent;
},
};
});Testing Code Splitting Without Mocking
If you need to test the actual dynamic import and the
Suspense fallback state (such as a loading spinner), you
should not mock the component. Instead, use React Testing Library’s
asynchronous query utilities, such as findBy, to wait for
the lazy component to load:
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders loading fallback and then the real component', async () => {
render(<App />);
// Verify that the fallback loading text is visible first
expect(screen.getByText('Loading...')).toBeInTheDocument();
// Wait for the dynamic import to resolve and render the lazy component
const profileHeading = await screen.findByRole('heading', { name: /profile/i });
expect(profileHeading).toBeInTheDocument();
});