Testing React 16.3+ Components with react-test-renderer (without Enzyme)

Russell Briggs
ITNEXT
Published in
4 min readJul 12, 2018

--

Shamelessly stolen from Algolia’s 2015 article on the same topic :)

I‘ve recently started a new React front-end project and have been making good use of the new React 16.3 Context API to make deep-sharing of data much cleaner. However while writing the tests using my go-to react test library Enzyme, I came across this issue which was a bit of a showstopper for me!

Sadly there hasn’t been any new Enzyme releases for 6 months, and they’ve not been able to tell me when the next release will be, so I took the decision to try out the standard React Test Renderer, which is shipped with the library itself (so should always be up-to-date!)

The documentation for React Test Renderer is somewhat light on helpful examples, so I wanted to record my findings here!

Mounting Your Components For Testing

Similarly to Enzyme, you use a TestRenderer.create() method to create a component tree ready for testing, as shown in the example below:

You’ll notice there are two main types of objects we use with the test renderer:

renderer as its name suggests, refers to the “wrapper” around your components. There are only a couple of methods on this object that I’ve found useful:

  1. renderer.toJSON() — this returns a JavaScript object representing the HTML (or react native) output of your components.
  2. renderer.toTree() — this returns a JavaScript object representing both your Component structure AND the HTML output

In tests running in NodeJS, you’ll want to JSON.stringify() the above to be able to see the full depth!

instance is a reference to the root component of your react component tree. You’ll use this object to make your assertions such as instance.findAll() . See examples below :)

Locating Components in the Tree

Unlike Enzyme, there are no CSS selectors in react-test-renderer. There are basically 3 ways to find components

  1. Using the instance.find() or instance.findAll() methods, which take a “predicate” function (e.g. (el) => el.type == 'span' ) and run it against every component in the tree.
  2. Using instance.findByType or instance.findAllByType() methods which take a component constructor and return matching components.
  3. Using instance.findByProps or instance.findAllByProps() , which search for components by the props you specify.

Example: Searching for a <button> with specific text

The assertion below will find a<button>Save Changes</button> tag, and check that it is disabled.

const saveButton = instance.find(
(el) => el.type == 'button'
&& el.children
&& el.children[0] == 'Save Changes'
);
expect(button.props.disabled).toEqual(true);

It is worth noting that find() will match exactly one component. If it does not match any, or matches more than one then it throws an exception.

Finding tags by their properties

The assertion below finds an <input type="radio" /> tag

const radio = instance.find(
(el) => el.type == 'input'
&& el.props.type == 'radio'
);

Testing for the absence of components

Since the find() function must match a component, we can’t use it to test for the absence of one, but we can use findAll() for that :)

const checkedBoxes = instance.findAll(
(el) => el.type == 'input'
&& el.props.type == 'checkbox'
&& el.props.checked == true
);
expect(checkedBoxes).toHaveLength(0);

Searching for components inside other components

Like Enzyme, you can search a sub-tree of components. In the example below, we look for all <option> tags inside a specific<select>

const select = instance.find(
(el) => el.type == 'select'
&& el.props.name == 'regions');
const options = select.findAll(
(el) => el.type == 'option');
expect(options.length).toEqual(regionData.length);

Searching for components by Constructor and reading properties

If you want to write your assertions based on React component properties, rather than their output, you can do that by searching by a component constructor.

The below assertion will match a <CardHeader /> react component, and check its title prop.

import CardHeader from '@material-ui/core/CardHeader';const cardHeader = instance.findByType(CardHeader);expect(cardHeader.props.title).toEqual('My awesome app');

Testing Event Handlers

Fortunately, it is pretty straightforward to test event handlers and their outcomes using the React Test Renderer. It works pretty much as it does in Enzyme.

All you need to do is get a reference to the component (e.g. using find()), then trigger the event handler directly via props , for example:

const select = instance.find(
(el) => el.type == 'select');
// trigger the onChange event for the select box
select.props.onChange({
target: { value: 'blue' }
});
expect(colourChangerMock).toHaveBeenCalledWith('blue');

Note — you are responsible for creating a suitable Event object, depending on what your event handler needs. The onChange example above should work for most input fields.

Its also worth noting that the rendered component tree seems to get updated as it would in the browser, so if your event handler triggers component updates, then you should be able to check for them straight away. I’ve not needed to call any updade() method with React Test Render like I have in the past with Enzyme.

Conclusion

Fortunately the React Test Renderer seems to support all the basic functionality needed for testing react components, with the advantage that it is in the same repository as the main React package, so it is more likely to work with the latest versions! (I see they are adding Async rendering support at the moment :) )

Hopefully this article helps someone struggling with the lack of comprehensive documentation for the test renderer like I was :)

--

--

CTO at Junari Ltd, the leading provider of customised management software for business — junari.com