How to Mock React Reconciliation
React’s reconciliation algorithm is responsible for diffing the
virtual DOM and updating the actual DOM. While you rarely need to mock
the diffing algorithm itself, testing asynchronous updates, concurrent
features, or complex state transitions requires control over when and
how reconciliation runs. This article covers how to mock and control
React reconciliation in your test environment using Jest,
act(), and the mock scheduler.
Why You Mock Reconciliation Effects, Not the Algorithm
Directly mocking the internal reconciliation engine
(react-reconciler) is highly discouraged because it is an
internal implementation detail subject to change. Instead, mocking
reconciliation means controlling the timing of state updates and
rendering queues.
In testing, you achieve this by: 1. Flushing updates
synchronously using act(). 2. Mocking the
React Scheduler to control when concurrent updates are
processed. 3. Mocking Custom Renderers if you are
testing non-DOM environments (like Canvas or WebGL).
Method 1: Using
act() to Control Reconciliation
The most common way to mock or force a reconciliation cycle in unit
tests is by wrapping state-changing operations in act().
This tells React to process all queued updates and complete
reconciliation before executing the next line of code.
import { render, screen } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import MyComponent from './MyComponent';
test('flushes reconciliation cycle', () => {
render(<MyComponent />);
const button = screen.getByRole('button');
// act() forces the reconciliation cycle to run and complete
act(() => {
button.click();
});
expect(screen.getByText('Updated State')).toBeInTheDocument();
});Method 2: Mocking the React Scheduler
For advanced testing—especially when dealing with React 18’s
concurrent features like useTransition or
useDeferredValue—you need to control the scheduling of
reconciliation tasks. React uses the scheduler package
internally to prioritize updates.
You can mock this scheduler to manually step through reconciliation phases.
1. Configure the Jest Mock
Create a mock for the React scheduler using the official
scheduler/unstable_mock package.
// jest.config.js or setupTests.js
jest.mock('scheduler', () => require('scheduler/unstable_mock'));2. Control Reconciliation in Your Test
Using the mocked scheduler, you can assert on what is scheduled, flush pending jobs up to a certain point, and control the virtual passage of time.
import * as React from 'react';
import { render } from '@testing-library/react';
import Scheduler from 'scheduler/unstable_mock';
import ConcurrentComponent from './ConcurrentComponent';
test('manually steps through concurrent reconciliation', () => {
render(<ConcurrentComponent />);
// Trigger a low-priority state update (e.g., inside startTransition)
const triggerButton = screen.getByRole('button');
triggerButton.click();
// Verify that reconciliation has been scheduled but not flushed yet
expect(Scheduler).toHaveYielded([]);
// Flush all pending reconciliation tasks
Scheduler.unstable_flushAll();
// Now the reconciliation is complete, and the DOM is updated
expect(screen.getByText('New Concurrent Content')).toBeInTheDocument();
});Method 3: Mocking a Custom Reconciler
If your application uses a custom renderer (such as
react-three-fiber or a custom canvas renderer), you mock
reconciliation by mocking the host config passed to
react-reconciler.
Instead of importing the real reconciler, mock the dependency to return a simplified version of the node attachment operations.
jest.mock('react-reconciler', () => {
return () => ({
createContainer: jest.fn(),
updateContainer: jest.fn(),
getPublicRootInstance: jest.fn(),
// Mock other required host config methods as empty functions
appendChild: jest.fn(),
removeChild: jest.fn(),
insertInContainerBefore: jest.fn(),
});
});By mocking these host methods, you bypass the actual DOM operations while allowing React’s component lifecycles to execute normally during your tests.