react-test-renderer
react-test-renderer renders a React tree to plain JavaScript objects instead of to the DOM or to native views, which is what made Jest snapshot testing of components possible before jsdom became the normal way to do it. You call create(<Component />), then toJSON() for a snapshot or root.findByType() to walk the tree by component type and props. Its own README now opens with a deprecation notice: the package is no longer maintained, it will be removed in a future React version, and React 19 prints a console warning when you call create(). The React team points at @testing-library/react instead.
Deprecated by the React team, unmaintained, and warning on every render under React 19; the 8.3M weekly downloads are inertia from old suites, not a recommendation. Keep it only long enough to migrate to Testing Library.
Use it if
- You have an existing snapshot suite built on it and need it to keep passing while you plan a migration
- You are working on a custom React renderer or reconciler, where there is no DOM to render into and the object tree is the thing you want to assert on
- You need to find nodes by component type or by props, which is exactly the internals-flavoured querying that DOM-based tools deliberately do not offer
- You are on an older React version where the recommended replacements do not yet apply cleanly
- You are starting a new test suite: the package is deprecated by its own maintainers and scheduled for removal, so anything you write on it has a deadline
- You are on React 19, where every create() call logs a deprecation warning that will either pollute your output or get suppressed and then forgotten
- You want tests that survive refactors, since asserting on the rendered object tree couples your tests to React internals that can change without notice
- You are testing React Native, where the team now recommends @testing-library/react-native for integration tests
- You need anything DOM shaped (events, focus, accessibility roles, layout), because there is no DOM here to have any of it
Setup reality
Installing is unremarkable: the version tracks React itself, and the peer dependency pins it to the matching React major, so react-test-renderer 19.x needs react 19.x and a mismatch is an install error rather than a runtime surprise. What you actually spend time on is the missing DOM. Refs are called with null unless you supply createNodeMock, so any component that measures or focuses a node needs a hand-written stub per test. Anything asynchronous needs wrapping in act, which now comes from react itself rather than from this package. And the deprecation warning in React 19 lands on every create() call, so an existing suite goes noisy the moment you upgrade React, which is usually the moment teams discover they were still depending on this.
Patterns
Render a component to a JSON treesnapshot
import TestRenderer from 'react-test-renderer';
const testRenderer = TestRenderer.create(
<Link page="https://example.com">Example</Link>
);
expect(testRenderer.toJSON()).toMatchSnapshot();Under React 19 this call also prints a deprecation warning, once per create, to your test output.
Wrap updates in actact
import { act } from 'react';
import TestRenderer from 'react-test-renderer';
let testRenderer;
await act(async () => {
testRenderer = TestRenderer.create(<Async />);
});Import act from react, not from react-test-renderer; the re-export exists but is on the same deprecation path as the rest of the package.
Find a node by component typefind-by-type
const root = testRenderer.root;
const button = root.findByType(Button);
expect(button.props.disabled).toBe(true);findByType throws when the match count is not exactly one; findAllByType is the version that returns an array.
Find nodes by their propsfind-by-props
const items = testRenderer.root.findAllByProps({ role: 'listitem' });
expect(items).toHaveLength(3);This matches React props, not DOM attributes, which is precisely the internals coupling the deprecation notice warns about.
Trigger behaviour by calling a propinvoke-handler
import { act } from 'react';
const button = testRenderer.root.findByType(Button);
act(() => {
button.props.onClick();
});There are no synthetic events here, so you call the handler directly; nothing about bubbling, default prevention or disabled state is simulated.
Give refs something to point atnode-mock
const tree = TestRenderer.create(<TextareaAutosize />, {
createNodeMock: (element) =>
element.type === 'textarea'
? document.createElement('textarea')
: null,
}).toJSON();Without createNodeMock every ref callback receives null, which breaks any component that measures or focuses its own node.
Re-render with new propsupdate
testRenderer.update(<Counter count={2} />);
expect(testRenderer.toJSON()).toMatchSnapshot();update re-renders the root in place, so state inside the tree survives; it is the equivalent of rerender in Testing Library.
Unmount and check cleanupunmount
testRenderer.unmount();
expect(clearIntervalSpy).toHaveBeenCalled();Unmount in an afterEach or long suites leak timers and subscriptions between tests.
Get the tree with component nodes includedto-tree
const tree = testRenderer.toTree();
console.log(tree.type, tree.props, tree.rendered);toJSON gives you host elements only; toTree keeps your own components in the output, and is heavier and more internals-flavoured for it.
Reach the class component instanceinstance
const instance = testRenderer.getInstance();
expect(instance.state.open).toBe(false);Returns null for function components, which is most code now; reaching for it is a sign the test is asserting on implementation.
Keep the deprecation warning visible on purposesilence-warning
// jest.setup.js
const realError = console.error;
console.error = (...args) => {
if (String(args[0]).includes('react-test-renderer is deprecated')) {
return; // tracked in TICKET-123, remove this block when migrated
}
realError(...args);
};If you must suppress it, tie the suppression to a ticket; a blanket console.error filter also hides the React warnings you do want to see.
What the same test looks like after migratingmigrate
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
render(<Link page="https://example.com">Example</Link>);
await userEvent.click(screen.getByRole('link', { name: 'Example' }));Queries move from component type to accessible role and text, which is the point: the test stops knowing your component structure.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| @testing-library/react | npm | Testing web components the way the React team now recommends, against a real DOM with standard queries |
| @testing-library/react-native | npm | Testing React Native components, which is the replacement the deprecation notice names |
| jsdom | npm | You want the DOM environment the README points to and intend to assert with plain DOM APIs rather than a testing library |