How we unit test React components using expect-jsx

Vincent Voyer |
Nov 3rd 2015 |
9 min read
|
Engineering

React was designed to solve one problem—how to build applications with data that changes over time. Considering the as-you-type search experience offered by Algolia wherein whole results pages, hits and refinement links alike, are updated at each keystroke, React was the obvious choice when we needed a tool to build out our new library of UI widgets: instantsearch.js.

We quickly struggled, however, with the unit test best practices for React components. We jumped from Github issues in facebook/react to blog posts and discuss.reactjs.org but couldn’t find a clear unit testing strategy.

Not too bad. Shallow rendering provides a way to render a React element onlyone level deep. This means that if you have children elements (like Label in our Button), it will not trigger their render methods. Very nice for unit testing!

The shallow rendering technique is simple to use and well suited for unit testing as it takes a React Element as INPUT and produces a React Element as OUTPUT.

There are other techniques available, but this is the currently recommended one and we expect (?) the React team to develop more tools around it in the future.

But wait, it’s not over! Did you see that diff? You passed some nice expected JSX (<div><Label name=”Marie” /></div>), and all you got was a weird object output diff.

This is because JSX is transpiled to a React.createElement call that then returns a React element (a JavaScript object). So when doing expect(something).toEqual(somethingElse), you are just comparing two JavaScript objects.

It would be better to get something like this:

Let’s make it happen! OK, we need a bit of tooling to get there.

What is the rackt team using?

After we found out about shallow rendering, we needed a good stack to make assertions and run the tests. We decided to have a look at what rackt, the Github org responsible for react-router and redux (two very popular react libraries) was using. After all, the closer you are to the tools of your community, the easier it is to get contributions.

Simulating events like onClick

This means you have to resort to directly calling the underlying function of your handlers props. Doing so doesn’t introduce any test-specific nonsense, as it’s the same thing Simulate would do in the end.

As you can see on line 24, we’re just calling the props.onClick handler directly to check that our custom handler has been called properly. Easy, right?

Well, that’s the way we unit test our component handlers. If you have a better solution, leave it in the comments!

Testing using references

Shallow rendering does not have access to any references you may have defined in your Component, so you cannot use them when testing with the shallow rendering technique. This is being investigated by the React team.

In the meantime test your refs dependent Components using the standard Test Utilities.

npm run test:watch

At Algolia, we try to minimize the entry cost to a project for any developer. All projects have an npm run test:watch task that we can use to TDD with a rapid feedback loop.

I am sorry I did not get your issue about “Comparing actual and expected JSX – common dude, you serious?” How do you test the structure of your render method as part of your unit testing strategy?

Yet you can use TestUtils.simulate but that’s a different need, first we need to ensure the structure that is generated is the one we are expecting (nodes, text, classes ..) and then we want to test the behavior of the handlers.

For the first part (structure), comparing JSX is a nice solution, there are others and maybe that’s what you wanted to say?

For the second part we do say that if you need it you can use TestUtils.simulate and use mocha-jsdom as we already use also at Algolia.

Thanks so much for putting this together! The boiler plate project on GitHub is really useful 😀

I followed all these steps and tried running my test command, only for npm to tell me that it couldn’t find “babel/register”, I presume because some of the changes to babel in version 6, I think? I’m not entirely sure what’s behind it, but to get around this, I had to run `npm install babel-register`, and in my mocha.opts file, change `js:babel/register` to `js:babel-register`, which did the trick 😀

oksas

Update! Apparently in the latest babel, the `register` bit is now part of the `babel-core` package and not the `babel` package, so a better solution to what I said above would be to simply use the `babel-core` package instead of `babel` (ie your package.json’s dependencies should have `babel-core` and not `babel`, I think?), and in mocha.opts, change `js:babel/register` to `js:babel-core/register` and all will be well. No need to separately install `babel-register` or anything like that.