Jack Franklin

An introduction to testing React components with Enzyme 3

In today's post we'll introduce the AirBnB library Enzyme for testing React applications. We'll do this using a test driven development (TDD) approach. That is, we'll write the tests first, watch them fail, and then build the React component out to fix the tests, before then writing more. We'll then consider how we can refactor code whilst running the tests to confirm we haven't made any errors.

In reality, I don't often write components from scratch in a TDD way, however I will often use TDD to replicate an existing bug in a component to first see the bug in action, and then fixing it. Feedback via test results on the command line is often much quicker than browser refreshes and manual interactions, so writing tests can be a very productive way to improve or fix a component's behaviour.

Set up

I'll be using a brand new React app for this tutorial, which I've created with create-react-app. This comes complete with Jest, a test runner built and maintained by Facebook.

There's one more dependency we'll need for now - Enzyme. Enzyme is a suite of test utilities for testing React that makes it incredibly easy to render, search and make assertions on your components, and we'll use it extensively today. Enzyme also needs react-test-renderer to be installed (it doesn't have it as an explicit dependency because it only needs it for apps using React 15.5 or above, which we are). In addition, the newest version of Enzyme uses an adapter based system where we have to install the adapter for our version of React. We're rocking React 16 so I'll install the adapter too:

yarn add -D enzyme react-test-renderer enzyme-adapter-react-16

The -D argument tells Yarn to save these dependencies as developer dependencies.

You can read more about installing Enzyme in the docs.

Enzyme setup

You also need to perform a small amount of setup for Enzyme to configure it to use the right adapter. This is all documented in the link above; but when we're working with an application created by create-react-app, all we have to do is create the file src/setupTests.js. create-react-app is automatically configured to run this file before any of our tests.

import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

If you're using an older version of React in your projects but still want to use Enzyme, make sure you use the right Enzyme adapter for the version of React you're using. You can find more on the Enzyme installation docs.

create-react-app is configured to run this file for us automatically when we run yarn test, so before our tests are run it will be executed and set up Enzyme correctly.

If you're not using create-react-app, you can configure Jest yourself to run this file using the setupTestFrameworkScriptFile configuration option.

The Hello component

Let's build a component that takes a name prop and renders <p>Hello, name!</p> onto the screen. As we're writing tests first, I'll create src/Hello.test.js, following the convention for test files that create-react-app uses (in your own apps you can use whichever convention you prefer). Here's our first test:

import React from 'react';
import Hello from './Hello';
import { shallow } from 'enzyme';

it('renders', () => {
const wrapper = shallow(<Hello name="Jack" />);
expect(wrapper.find('p').text()).toEqual('Hello, Jack!');
});

We use Enzyme's shallow rendering API. Shallow rendering will only render one level of components deep (that is, if our Hello component rendered the Foo component, it would not be rendered). This helps you test in isolation and should be your first point of call for testing React components.

You can run yarn test in a React app to run it and have it rerun on changes. If you do that now, you'll see our first error:

Cannot find module './Hello' from 'Hello.test.js'

So let's at least define the component and give it a shell that renders nothing:

import React from 'react';

const Hello = props => {
return null;
};

export default Hello;

Now we get a slightly cryptic error:

Method “text” is only meant to be run on a single node. 0 found instead.

Once you've used Enzyme a couple of times this becomes much clearer; this is happening because we're calling wrapper.find('p') and then calling text() on that to get the text, but the component is not rendering a paragraph. Let's fix that:

const Hello = props => {
return <p>Hello World</p>;
};

Now we're much closer!

expect(received).toEqual(expected)

Expected value to equal:
  "Hello, Jack!"
Received:
  "Hello World"

And we can make the final leap to a green test:

const Hello = props => {
return <p>Hello, {props.name}!</p>;
};

Next up, let's write a test to ensure that if we don't pass in a name, it defaults to "Unknown". At this point I'll also update our first test, because it('renders', ...) is not very descriptive. It's good to not care too much about the name of the first test you write, and focus on the implementation, but once you're more comfortable with what you're testing and beginning to expand your test suite, you should make sure you keep things organised.

With our second test, we're failing again:

it('renders the name given', () => {...})

it('uses "Unknown" if no name is passed in', () => {
const wrapper = shallow(<Hello />);
expect(wrapper.find('p').text()).toEqual('Hello, Unknown!');
});
expect(received).toEqual(expected)

Expected value to equal:
  "Hello, Unknown!"
Received:
  "Hello, !"

But we can now write our first pass at the implementation to fix it:

const Hello = props => {
return <p>Hello, {props.name || 'Unknown'}!</p>;
};

And now the test is green we're free to refactor. The above is perfectly fine but not the way it's usually done in React. Some might choose to destructure the props argument and give name a default value:

const Hello = ({ name = 'Unknown' }) => {
return <p>Hello, {name}!</p>;
};

But most of the time when working with React components I'll use the defaultProps object to define the defaults. I'll also set the component's propTypes:

import React from 'react';
import PropTypes from 'prop-types';

const Hello = props => {
return <p>Hello, {props.name}!</p>;
};

Hello.propTypes = {
name: PropTypes.string,
};

Hello.defaultProps = {
name: 'Unknown',
};

export default Hello;

And all our tests are still passing.

Conclusion

That brings our first look at testing React with Enzyme 3 to an end. In future tutorials we'll dive further into what Enzyme has to offer and see how we can test components of increasing complexity.