How to Test Code Splitting in React

Testing code splitting in React ensures that dynamically imported components load correctly and that your application handles loading states gracefully. This article provides a straightforward guide on how to test React components configured with React.lazy and Suspense using Jest and React Testing Library. You will learn how to verify loading fallbacks, test the rendered outcome of asynchronous chunks, and handle mocked dynamic imports.

Understanding the Testing Challenge

Code splitting relies on dynamic import() statements, which are asynchronous by nature. When a component is loaded lazily using React.lazy(), React pauses rendering of that component and displays a Suspense fallback (like a spinner) until the chunk is fetched and resolved. To test this behavior, your tests must handle this asynchronous transition.

1. Testing the Suspense Fallback State

To verify that your application displays a loading indicator while the split code is being fetched, you need to test the initial render before the promise resolves.

import React, { Suspense } from 'react';
import { render, screen } from '@testing-library/react';

const LazyComponent = React.lazy(() => import('./MyComponent'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  );
}

test('should render the loading fallback initially', () => {
  render(<App />);
  expect(screen.getByText('Loading...')).toBeInTheDocument();
});

2. Testing the Loaded Component (Asynchronous Resolution)

To test the final rendered component after the dynamic import has successfully loaded, use React Testing Library’s asynchronous queries. The findBy* queries are designed for this scenario because they repeatedly query the DOM until the element appears or the timeout is reached.

import React, { Suspense } from 'react';
import { render, screen } from '@testing-library/react';

const LazyComponent = React.lazy(() => import('./MyComponent'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  );
}

test('should render the lazy component after loading', async () => {
  render(<App />);

  // Wait for the fallback to disappear and the real component to render
  const lazilyLoadedElement = await screen.findByText('Hello from Lazy Component');
  expect(lazilyLoadedElement).toBeInTheDocument();
  expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
});

3. Mocking Lazy Components for Unit Tests

If you want to isolate your tests and avoid loading actual network chunks during unit testing, you can mock the dynamic import using Jest. This is useful when you want to focus on testing the parent component’s logic without waiting for real asynchronous resolutions.

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

// Mock the component that is lazily loaded
jest.mock('./MyComponent', () => {
  return function DummyComponent() {
    return <div data-testid="mocked-lazy">Mocked Component</div>;
  };
});

test('renders mocked component instantly', () => {
  render(<App />);
  expect(screen.getByTestId('mocked-lazy')).toBeInTheDocument();
});

By combining fallback state assertions, asynchronous queries like findBy, and module mocking, you can thoroughly test the behavior and robustness of code-split applications in React.