Testing React with Enzyme

Kevin Salter
Kevin Salter’s Blog
7 min readMay 18, 2016

--

At 7Geese we’ve been working on converting a large codebase built with Backbone/Marionette and written in CoffeeScript to React written in ES6. We’re still using Backbone models and collections as the data layer for now, but all views moving forward are being written in React. In this post I want to take a minute to walk through a practical example of testing React elements while sharing my experience using AirBnB’s JavaScript testing utilities for React, Enzyme.

The Setup

I took the example repo described in the Using Enyzme with Webpack part of Enzyme’s website as an easy way to get started. There’s a repository on GitHub for this post with all of the npm packages up-to-date, including the latest version of React, Sinon.js, and the nyan cat reporter, ’cause this is supposed to be fun! I also added the npm run start command so you can view the components being tested in a browser. Clone the repo and checkout the README for more details.

We’re going to write unit tests for three components that work together to indicate loading, fetch data (using the GitHub API to get a list of random users), then show that data as a list on the page. We’ll be testing that components render the desired and expected output given some props, and that the correct handlers are called based on a user’s interaction with the components.

<Container />

The <Container /> component is named after the technique of using Container Components to fetch data that will then be passed down to the sub-components it renders. Since we will need to use our own data fetching calls and class methods like componentDidMount(), we’re using React’s ES6 class syntax.

We’re using ES6+ Property Initializers for propTypes, defaultProps, and state. You’ll notice that I provided a default for the loadingTimeout prop, even though it’s marked as isRequired. For a while I was going by this idea:

This meant that I hadn’t been in the habit of using isRequired for props that had a default value. Then I came across this bit of info in the Flow docs:

Note that it’s still a good idea to specify isRequired, even when a default value is provided, to protect against null prop values. React will only use a default value if the prop value is undefined.

…good to know!

When <Container /> mounts, it shows a loading indicator, fetches a list of users from the GitHub API, then hides the loading indicator and displays the list along with a “refresh” button. If you’ve cloned the repo, run npm run start from the command line and navigate to http://localhost:3333/ in your browser to see it in all its glory! 👌🏻

Looking inside /test/container.spec.js you’ll find our first set of basic tests.

JavaScript unit tests are typically testing either the behaviour or aesthetics of the code under test. In the first two test blocks we’re testing for aesthetics, that is to say, we’re testing that the correct DOM is rendered. We can use Enzyme’s shallow method to isolate our tests to only the <Container /> component. From the Enzyme docs:

Shallow rendering is useful to constrain yourself to testing a component as a unit, and to ensure that your tests aren’t indirectly asserting on behavior of child components.

Enzyme returns a wrapper where we can use jQuery-like selectors to get the elements of the DOM that we want to make assertions against. In this case we test that a container <div> is rendered with wrapper.first(‘div’), and we test that it contains the <Loading /> component with wrapper.find(‘Loading’). Since this is what we expect to happen by default, we’re done with the first test!

Quick tip: To help get your bearings when selecting elements in your tests, you can log an HTML-like string representation of the component under test to the console by calling console.log( wrapper.debug() )

Also, in the first test I’m only looking for the first <div> element and not necessarily asserting that is has a certain class. That’s a matter of preference, in this case, to be less specific because I’m always trying to minimize unit tests’ reliance on a specific tree structure, and attempting to make the tests less brittle by ignoring the DOM as much as possible and focusing on the content.

In the next test we’re again shallow rendering <Container />, but this time we’re calling wrapper.setState({loading: false}) to get the component into the state we’d like to test. We’re not testing the state itself, but updating its config to get the component to show the expected rendered output. We test that <MainView /> is rendered with wrapper.find(‘MainView’).

Our last basic test for <Container /> uses Enzyme’s mount method instead of shallow. This actually renders the component into a JavaScript implementation of the DOM called jsdom (the setup for which can be found in /test/.setup.js). The Enzyme docs explain the mount method:

Full DOM rendering is ideal for use cases where you have components that may interact with DOM apis, or may require the full lifecycle in order to fully test the component (i.e, componentDidMount etc.)

Here we’re using a spy from Sinon.js to assert that the componentDidMount() method gets called when the component is mounted. As a quick refresher, if you spy on a function, the function’s behaviour is not affected. If you wanted to change how a function behaves, you’d need to use a stub. A stub then allows you to do all kinds of fun and powerful things like returning mock data, which can be useful for simulating API calls without having to actually make an AJAX request.

<Loading />

The next two components that we’re going to test are written using React’s Stateless Function syntax.

Stateless components are so nice to test because they are just functions that take props and return some DOM.

As a best practice, I typically aim to make as many stateless components as possible in the apps I’m writing, pushing state and logic up the component tree and into parent components whenever possible.

When I need to pass props into a component being tested, I like to declare them as an object inside the describe block, and then pass them into the component using the spread operator, like this:

<Loading {…LOADING_PROPS} />

In the first test I am actually testing that the loading component has a certain CSS class when it’s rendered with wrapper.find(‘div.loading-image-centered’). This is because the styles associated with that class are critical for the component to display as intended.

Lastly, Enzyme makes it really easy to test that the component renders an image tag with correct src value, which we can check for by using loadingImage.prop(‘src’).

<MainView />

This time I set up the default props using a fixture for the array of ghUsers I expect (so I don’t have to actually make an AJAX call — the fixture is basically a copy/paste of what you see by visiting http://api.github.com/users). I’m using a spy() to stand in for the refreshUserList function that gets passed from <Container /> into this component because I’ll want to make some assertions about its behaviour. If I didn’t intend on making any assertions on that particular function, I would have created an empty function like

const noop = () => {};

and used that to replace the refreshUserList() method.

Speaking of the refreshUserList() method, I’d also like to point out that we’re using an arrow function to bind the method in <Container /> so that we can pass it into the <MainView /> sub-component without having to do something like declare refreshUserList.bind(this) in the constructor of the class, which I find to be a pretty gross syntax.

In the first block I’m testing that the component renders an unordered list, and that the list has children, i.e. list items. Again, to make the test less brittle I don’t specify a specific length of expected list items in an effort to not couple the test too closely to DOM structure (even though we can expect 10 names to be returned from the GitHub API every time with the current implementation). In this situation I think it’s OK to not specify the length, but sometimes you’ll find situations where the number of children is significant and you’ll want to make more fine-grained assertions in that case.

In the second test we see how easy Enzyme makes it to test events and event handlers. By simply finding the button and calling refreshUserListButton.simulate(‘click’), we can assert that the callback is in fact fired when that button is clicked and the function has been called once.

npm run test

So go ahead and run npm run test from your command line and watch the nyan cat reporter show you that those tests pass! Also, feel free to use the demo repo as an easy way to start playing around with some of Enzyme’s features. Enzyme is still a young library, but with the backing of AirBnB, a growing community, and dead simple API surface, it promises to make testing React components a breeze for some time to come.

Hey, if you like building cool things, collaborating with a super talented team, and want to work at a company with a great culture, 7Geese is hiring.

--

--