How to Test Lazy Loading in React

Testing lazy-loaded components in React ensures your application handles asynchronous code splitting and dynamic imports gracefully without breaking the user experience. This article provides a straightforward guide on how to test React components wrapped in React.lazy and Suspense using Jest and React Testing Library. You will learn how to verify that fallback loading indicators render correctly and how to wait for lazy-loaded content to resolve in your test environment.

The Challenge with Lazy Loading

React’s lazy function relies on dynamic imports, which return Promises. Because loading these components is an asynchronous operation, a standard synchronous test will fail because the component is not immediately present in the DOM. To test them successfully, you must handle the loading state (provided by Suspense) and wait for the Promise to resolve.

A Basic Example of Lazy Loading

Consider this simple component that lazily imports a child component:

// MyComponent.jsx
import React, { Suspense, lazy } from 'react';

const LazyChild = lazy(() => import('./LazyChild'));

export default function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyChild />
      </Suspense>
    </div>
  );
}

Testing with React Testing Library

React Testing Library (RTL) provides asynchronous utilities like findBy queries and waitFor to handle components that load over time.

Here is how you write a test to assert both the fallback “Loading…” state and the final rendered component:

// MyComponent.test.jsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import MyComponent from './MyComponent';

test('renders fallback first and then the lazy-loaded component', async () => {
  render(<MyComponent />);

  // 1. Assert that the fallback loader is visible initially
  expect(screen.getByText('Loading...')).toBeInTheDocument();

  // 2. Wait for the lazy component to resolve and render
  // findBy queries automatically retry until the element appears or the timeout is reached
  const lazyElement = await screen.findByText('Lazy Child Content');
  expect(lazyElement).toBeInTheDocument();

  // 3. Assert that the fallback loader has been removed from the DOM
  expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
});

Mocking Lazy Components for Unit Tests

If you want to isolate the parent component and avoid testing the actual dynamic import of the child component, you can mock the dynamic import at the top of your test file. This is highly useful for unit tests where you only want to verify that Suspense behaves correctly without loading real chunks.

// MyComponent.mock.test.jsx
import React from 'react';
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';

// Mock the LazyChild component import
jest.mock('./LazyChild', () => {
  return function DummyLazyChild() {
    return <div>Mocked Lazy Child</div>;
  };
});

test('renders the mocked lazy component', async () => {
  render(<MyComponent />);

  const lazyElement = await screen.findByText('Mocked Lazy Child');
  expect(lazyElement).toBeInTheDocument();
});

Using these asynchronous patterns and mocking strategies allows you to write resilient tests for your React applications that use code splitting, keeping your test suite fast and reliable.