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();
});