mrkeyoor.com_
Sun 09 Aug 06:56 UTC
npmTestingupdated 09 Aug 2026

react-shallow-renderer

A DOM-free test renderer that executes one custom React component and returns the React element it produced without rendering child components. Tests can inspect the root element's type, props, and children, rerender with new props, or reach a mounted class instance. It supports much of the React 18-era component model, including function components, class components, memo, forwardRef, and state hooks, but deliberately skips refs, effects, and post-commit lifecycles. It is now a compatibility tool for React 16 through 18, not a current React testing default.

Verdict

Keep it only where a React 16 through 18 test suite already depends on shallow semantics. Do not install it for React 19 or a new project; Testing Library catches behavior and integration failures that one-level element inspection cannot see.

API stability3/5For supported React 16 through 18 projects, the small public API of constructor, render, getRenderOutput, getMountedInstance, and unmount has stayed recognizable for years. Version 16.15.0 also models memo, forwardRef, several hooks, and class updates. The weak point is its dependency on React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED and hard-coded React element symbols, so the API can remain textually unchanged while a React upgrade breaks it underneath.
Docs2/5The README gives a clear explanation of one-level rendering, one complete assertion example, installation commands, a refs warning, and short descriptions of render and getRenderOutput. It does not document getMountedInstance, unmount, hook behavior, effect no-ops, skipped class lifecycles, memo and forwardRef handling, the host-element error, module formats, React 19 incompatibility, or the absence of query and event helpers. Its recommendation to consider Enzyme also reflects an older React testing landscape.
Maintenance1/5The latest npm release, 16.15.0, shipped on April 6, 2022 to add React 18 support, and the latest normal repository commit is from the same day. GitHub's March 2023 push corresponds to an unmerged dependency pull request rather than a release. The repository is not archived and the package is not registry-deprecated, but React 19 support has remained an open issue since April 2025 with an unmerged patch since August 2025.
Ecosystem2/5The package received 3,058,790 npm downloads for July 31 through August 6, 2026, largely because older React and testing dependency trees still include it. The resolved enzymejs repository has 37 stars and 14 forks, its peer range stops at React 18, and there is no higher-level query or plugin ecosystem around the renderer itself. Current web and React Native testing libraries support React 19 and have become the documented path for behavior-focused component tests.

Use it if

  • You maintain an existing React 16, 17, or 18 suite whose assertions intentionally depend on one-level rendering
  • You need fast structural tests for a pure component without jsdom, a native host environment, or child execution
  • You are preserving a React Native test suite on a compatible React version and only inspect emitted element props
  • You need direct access to a shallow-rendered class instance to support a gradual migration of older tests
Skip it if

Setup reality

Install it as a development dependency, but check React first. Version 16.15.0 declares react ^16.0.0, ^17.0.0, or ^18.0.0 as its peer and installs object-assign plus react-is; React 19 is outside that contract. There is no test runner, assertion library, JSX transform, DOM, or native renderer included, so Jest, Vitest, Babel, TypeScript, and source transforms remain your responsibility. The package publishes CommonJS, ESM, and UMD files, but package.json exposes only the CommonJS index and has no exports or module field. It also ships no TypeScript declarations. Basic use is synchronous: create a new ShallowRenderer, pass a custom component element to render, then inspect getRenderOutput. Passing a host element such as <div /> directly throws because the root must be a function, class, memo, or forwardRef component. Children in the returned output are still React elements, not mounted instances, and there are no find, query, simulate, debug, or accessibility helpers. To exercise a handler, call the relevant output prop yourself; that is not equivalent to browser or React Native event dispatch. Hook state and reducers are modeled, but effect hooks are no-ops, transition state is simplified, useSyncExternalStore only reads getSnapshot, and refs are unsupported. Class componentDidMount and componentDidUpdate do not run, although componentWillUnmount runs when renderer.unmount() is called. The implementation reaches into React secret internals and carries React 18-era symbol knowledge, which is why forcing installation beside React 19 is unsafe even if a package manager can be persuaded. Pin React 18 and the renderer together for legacy suites. For new tests, use @testing-library/react in web projects or @testing-library/react-native in native projects and assert visible behavior instead of component structure.

Patterns

Render one function componentrender-function-component

import ShallowRenderer from 'react-shallow-renderer';

function Greeting({ name }) {
  return <h1>Hello, {name}</h1>;
}

const renderer = new ShallowRenderer();
renderer.render(<Greeting name="Ada" />);
const output = renderer.getRenderOutput();

expect(output.type).toBe('h1');
expect(output.props.children).toEqual(['Hello, ', 'Ada']);

The root passed to render must be a custom component. Passing <h1> directly throws.

Inspect props on the emitted host elementassert-output-props

renderer.render(<Button disabled label="Save" />);
const output = renderer.getRenderOutput();

expect(output.type).toBe('button');
expect(output.props.disabled).toBe(true);
expect(output.props['aria-label']).toBe('Save');

This inspects a React element object. No button exists in a DOM or native host tree.

Verify a child is passed the right propskeep-child-shallow

renderer.render(<Profile userId="u-42" />);
const output = renderer.getRenderOutput();
const child = output.props.children;

expect(child.type).toBe(Avatar);
expect(child.props.userId).toBe('u-42');

Avatar is not executed. This can verify component wiring but cannot catch failures inside Avatar.

Normalize and inspect multiple childreninspect-multiple-children

import React from 'react';

renderer.render(<Toolbar actions={actions} />);
const children = React.Children.toArray(renderer.getRenderOutput().props.children);

expect(children).toHaveLength(2);
expect(children[0].type).toBe(SearchButton);
expect(children[1].type).toBe(MenuButton);

The renderer has no find or query API; use the returned element structure or switch to Testing Library for semantic queries.

Rerender the same component with new propsrerender-with-props

const renderer = new ShallowRenderer();
renderer.render(<Status online={false} />);
expect(renderer.getRenderOutput().props.children).toBe('Offline');

renderer.render(<Status online />);
expect(renderer.getRenderOutput().props.children).toBe('Online');

Reuse the same renderer and component type to exercise its update path; changing the root component type resets renderer state.

Call an emitted event prop directlyinvoke-callback-prop

const onSave = vi.fn();
renderer.render(<SaveButton onSave={onSave} />);

renderer.getRenderOutput().props.onClick();
expect(onSave).toHaveBeenCalledOnce();

Replace vi.fn with jest.fn under Jest. Calling a prop is not real event propagation, default behavior, focus management, or accessibility testing.

Observe a useState updatetest-hook-state

function Counter() {
  const [count, setCount] = React.useState(0);
  return <button onClick={() => setCount((n) => n + 1)}>{count}</button>;
}

renderer.render(<Counter />);
renderer.getRenderOutput().props.onClick();
expect(renderer.getRenderOutput().props.children).toBe(1);

State dispatch is modeled synchronously, but useEffect, useLayoutEffect, and useInsertionEffect never run.

Reach a legacy class instanceaccess-class-instance

class Counter extends React.Component {
  state = { count: 0 };
  increment = () => this.setState(({ count }) => ({ count: count + 1 }));
  render() { return <span>{this.state.count}</span>; }
}

renderer.render(<Counter />);
renderer.getMountedInstance().increment();
expect(renderer.getRenderOutput().props.children).toBe(1);

getMountedInstance returns null for function components. Prefer testing rendered behavior when migrating old class tests.

Check a memoized component's comparisontest-memo-rerender

const renderSpy = vi.fn();
const Label = React.memo(({ text }) => {
  renderSpy(text);
  return <span>{text}</span>;
});

renderer.render(<Label text="same" />);
renderer.render(<Label text="same" />);
expect(renderSpy).toHaveBeenCalledTimes(1);

The renderer applies React.memo's custom comparator or a shallow prop comparison, but this still does not exercise a committed host update.

Assert that a component renders nothingtest-conditional-null

function Notice({ visible }) {
  return visible ? <aside>Maintenance</aside> : null;
}

renderer.render(<Notice visible={false} />);
expect(renderer.getRenderOutput()).toBeNull();

getRenderOutput returns the component's value directly, including null, strings, or React elements.

Snapshot the shallow React elementsnapshot-output

renderer.render(<AccountCard account={account} />);
expect(renderer.getRenderOutput()).toMatchInlineSnapshot();

This snapshots one component's returned element tree, not browser HTML or React Native host output. Prefer focused assertions when snapshots churn often.

Exercise class cleanup on unmountunmount-class-component

const cleanup = vi.fn();
class Subscription extends React.Component {
  componentWillUnmount() { cleanup(); }
  render() { return <Channel id={this.props.id} />; }
}

renderer.render(<Subscription id="news" />);
renderer.unmount();
expect(cleanup).toHaveBeenCalledOnce();

componentWillUnmount runs for classes, but componentDidMount and componentDidUpdate are intentionally skipped. Function-component effect cleanups do not run because effects are no-ops.

Alternatives

PackageRegistryPick it when
@testing-library/reactnpmReact 18 or 19 web tests should exercise rendered DOM and user-visible behavior
@testing-library/react-nativenpmCurrent React Native tests need queries, events, async updates, and native-aware output
@storybook/react-vitenpmComponents need isolated browser rendering plus visual and interaction tests rather than shallow structure checks