Testing React Component Methods

Jeremy Gayed
On Frontend Engineering
5 min readJan 28, 2016

--

Update

Since posting this, Enzyme, a React testing library from Airbnb has come on the scene. It has a brilliant API for full, shallow and static rendering React components. There is little reason to use anything else.

Jest has also improved dramatically since Jest v15. I will write a follow-up post showing some of the amazing things you can do with Jest & Enzyme together.

I will leave the rest of this post here for posterity. 😁

Welcome to the Wild West

The world of React-based application development can definitely feel like the Wild West, especially now as the community is still piecing together documentation and examples on how various frameworks and tooling fit together (React, Webpack, Babel, ES6, Flux, Redux, etc). It can be a bit overwhelming at first, but once you get it going, it really is a pleasure to work with.

As I’ve gone along, I’ve encountered many gotchas and am learning a bit about what works and what doesn’t. As with all of this, your mileage may vary, but I greatly appreciated blog posts that detailed a particular nugget of information that helped me piece together a larger picture rather than having to rely on a complete start kit or boilerplate and reverse engineer the solution I needed.

To that end, I plan on writing a series of posts, each (hopefully) individually helpful to you in your project without having to rely on a particular stack setup for it to be useful.

Testing React components

Officially, Facebook recommends the Jest framework for testing React components. However, I’ve personally found it difficult to work with and quite a bit slower than instrumenting my own tests via Mocha. It may have improved since I last looked at it, but mocha has served us well so far.

Many of the tests I’ve seen, especially for UI-focused React components have a wide test bracket and are often testing things that are not very meaningful. In its simplest terms, searching for number or type of DOM nodes, or for specific class names being rendered is not the most meaningful test you could write. I’ll explain why later in this post.

Example Component

Let’s say we have the following React component:

import {React, Component, PropTypes} from 'react';

export default class FooComponent extends Component {
static propTypes = {
option: PropTypes.oneOf([
'bar', 'baz'
])
};

static defaultProps = {
option: 'bar'
};

getBar() {
return (
<span className="bar">
Bar
</span>
)
}

getBaz() {
return (
<span className="baz">
baz
</span>
)
}

render() {
switch (this.props.option) {
case 'bar': return this.getBar();
case 'baz': return this.getBaz();
default: return this.getBar();
}
}
}

In this particular example, you can see that the logic to render bar or baz sub-components (in this case, raw <span> tags) is relatively trivial. But you can imagine cases where this logic is non-trivial and warrants being tested.

Testing via class names

One way you could test this component is to render it into a tree with enumerations of all the different property values possible for option, and test that you can find the class you expect to see.

For example:

describe('FooComponent', () => {
it('Renders bar by default', () => {
const shallowRenderer = ReactTestUtils.render(
<FooComponent />
);
const output = shallowRenderer.getRenderOutput();

// Now you can inspect output as if it were a DOM tree
assert.lengthOf(ShallowTestUtils.findAllWithClass(output, 'bar'), 1); // only bar
assert.lengthOf(ShallowTestUtils.findAllWithClass(output, 'baz'), 0); // no baz
});
});

In this example, we’re utilizing the helpful react-shallow-testutils module to check that the class names of elements we expect to see are there.

You could then create 2 more test cases for <FooComponent option=”baz” /> and <FooComponent option=”bar” />.

This isn’t a very good test (the test bracket is too wide)

Sure, this test would work, and it would be a relatively reliable test — right up until the markup changes for this component. Or the class name for bar or baz changed to something else.

Then your tests would fail, and you suddenly realize you’re not just testing the complexity of the code paths for how the component behaves under different options passed in. Instead, your test bracket is wide enough to also include the resulting rendered markup and you can’t answer the question of whether the code path for different options has a bug or your rendered markup has a bug, or both!

How not to rely on class names for testing React components

It is generally bad practice to rely on class names, DOM structure, etc when writing tests since those can frequently change and cause meaningless and noisy test failures.

Instead, it would be great if we could use sinon to spy on the getBar() and getBaz() methods and assert that they are (or are not) called given the enumerated options to FooComponent. This would actually test the code path we’re concerned about.

Spy on the methods you expect to be called to narrow your test bracket

The gotcha here is that you can’t just spy FooComponent.prototype.getBar, as it doesn’t actually exist when imported.

React component methods do not exist until the component has been instantiated and rendered.

So, instead, we must first instantiate and (shallowly) render our component, then grab the instance to attach spies.

Helper method (getShallowlyRenderedInstance())

Because this was expected to be such a common pattern, I’ve defined a helper method, getShallowlyRenderedInstance():

getShallowlyRenderedInstance(component) {
const renderer = ReactTestUtils.createRenderer();
renderer.render(component);
return renderer._instance && renderer._instance._instance;
};

Then we can test it like so:

describe('FooComponent', () => {
it('Renders bar by default', () => {
const fooInstance = getShallowlyRenderedInstance(<FooComponent />);

// Now you can spy on instance methods
const barSpy = sinon.spy(fooInstance, 'getBar');

// Now render the instance to execute the expected code path
fooInstance.render();

// Assert that getBar was called as expected
assert(barSpy.called);
});
});

Side note on renderer._instance._instance

Obviously reading the _instance property of the renderer and the rendered output is an undocumented feature, but it was the only way I could find (thanks to react-shallow-testutils) that gave us access to the instance methods that we wanted to spy.

The first ._instance is the React renderer object that wraps the component we care about. Whereas the ._instance._instance is the rendered instance of the actual component in question.

Hopefully, this instance will be exposed in the official API in React 0.15 or later eventually.

NOTE: I’m sure there’s other ways of doing this, likely better than what I’ve outlined here. If you’ve found something that you feel works better, or is cleaner, etc. then please comment or ping me, I’d love to learn from you! :)

Originally published at Jeremy Gayed.

--

--

Coptic Orthodox Christian. Lead Software Engineer @nytimes. Lover of all things JavaScript 🤓