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.