React Test Renderer
The `react-test-renderer` package provides a renderer that can be used to render React components into plain JavaScript objects, offering a way to test components without requiring a DOM environment or a browser. It is primarily used for snapshot testing, enabling the generation of serializable JSON representations of a component tree that can be compared against previous snapshots to detect unintended UI changes over time. The current stable version is 19.2.5, aligning with the core React library's frequent patch releases. Unlike `@testing-library/react`, this library focuses on the internal structure and rendered output of components rather than simulating user interactions, making it suitable for asserting on the rendered tree itself.
Common errors
-
Warning: An update to MyComponent inside a test was not wrapped in act(...).
cause A component's state or props were updated, causing a re-render, but the update code was not enclosed within `act()`, leading to potential race conditions or incorrect test results, especially in React 18+.fixWrap the code block that initiates the update (e.g., `renderer.create`, `instance.update`, `fireEvent`, `setTimeout` callbacks) with `act(() => { ... });` or `await act(async () => { ... });` for async operations. -
TypeError: Cannot read properties of undefined (reading 'toJSON')
cause This typically occurs when `renderer.create(<MyComponent />)` returns `undefined` or `null`, often because the component failed to render or there was an error during rendering, before `toJSON()` could be called.fixEnsure the component renders without errors. Debug the component's render method or constructor to identify any issues. Also, verify that `tree` is assigned and not `undefined` before calling `tree.toJSON()`. -
Invariant Violation: Could not find an instance for the passed node.
cause This error can occur when trying to use `act()` with an unmounted component or an instance that is no longer valid in the React tree. It can also happen if `act` is called on an instance from a different renderer.fixEnsure that `act` is operating on a currently mounted component instance returned by the `react-test-renderer` `create` function, and that you are not mixing instances from different rendering contexts (e.g., `react-test-renderer` and `react-dom/test-utils`).
Warnings
- breaking Upgrading React to version 18 or higher introduces concurrent features that necessitate wrapping all state updates, component lifecycles, and asynchronous code (e.g., `setTimeout`, promises) that lead to component renders within `act()` calls. Failure to do so will result in warnings and potentially unstable tests.
- gotcha Snapshots can become brittle and lead to frequent test failures if they capture dynamic or non-deterministic data, such as `Date.now()`, unique IDs (UUIDs), or random numbers. This makes snapshots unreliable for detecting *meaningful* UI changes.
- deprecated Directly calling methods like `instance.setState()` or `instance.forceUpdate()` on the `TestRenderer` root or component instances is deprecated in favor of using the `act()` utility to queue and flush updates, especially in React 18+.
Install
-
npm install react-test-renderer -
yarn add react-test-renderer -
pnpm add react-test-renderer
Imports
- create
import { create } from 'react-test-renderer';import renderer from 'react-test-renderer'; const tree = renderer.create(<MyComponent />);
- act
import renderer from 'react-test-renderer'; renderer.act(() => {});import { act } from 'react-test-renderer'; - ReactTestRenderer
import { ReactTestRenderer } from 'react-test-renderer';import type { ReactTestRenderer } from 'react-test-renderer';
Quickstart
import renderer, { act } from 'react-test-renderer';
import React from 'react';
interface MyComponentProps { message: string; }
const MyComponent: React.FC<MyComponentProps> = ({ message }) => {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
const timer = setTimeout(() => setCount(c => c + 1), 50);
return () => clearTimeout(timer);
}, []);
return (
<div>
<h1>{message}</h1>
<p>Count: {count}</p>
</div>
);
};
describe('MyComponent snapshot tests', () => {
it('renders correctly', () => {
let tree;
act(() => {
tree = renderer.create(<MyComponent message="Hello World" />);
});
expect(tree?.toJSON()).toMatchSnapshot();
});
it('updates count after a short delay', async () => {
let tree: renderer.ReactTestRenderer | undefined;
act(() => {
tree = renderer.create(<MyComponent message="Test Update" />);
});
// Wait for the effect to run and update state
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 100)); // Simulate async update
});
expect(tree?.toJSON()).toMatchSnapshot();
const pElement = tree?.root.findByType('p');
expect(pElement?.children).toEqual(['Count: ', '1']);
});
});