Testing React Components with Enzyme and Jest

John Kariuki
👁️ 1,537 views
💬 comments

Enzyme is an open source JavaScript testing utility by Airbnb that makes it fun and easy to write tests for React. In this article, we will be going through writing tests for React using Enzyme and Jest.

To get started, you will need to familiarize yourself with the following:

  1. React - A JavaScript library for building delightful UI by Facebook
  2. Jest - A JavaScript testing framework by Facebook.

Why Jest?

Jest is a fast JavaScript testing utility by Facebook that enables you to get started with testing your JavaScript code with zero configuration.

Table of Contents

    This means that you can easily perform tasks like code-coverage by simply passing --coverage option when running your tests.

    Setting Up Our React App

    First things first, create a react application using create-react-app.

    create-react-app enzyme-tests
    cd enzyme-tests
    yarn start

    Setting Up Enzyme

    To get started with Enzyme, go ahead and install the library via yarn or npm as a dev dependency.

    yarn add --dev enzyme enzyme-adapter-react-16

    Notice that we also installed an adapter alongside enzyme that matches the React version installed by create-react-app.

    If you are not using the latest version of React, Enzyme has adapters for all versions of react from ^0.13.0 to ^16.0.0. You can read more about it in the Enzyme installation guide.

    src/enzyme.js

    import Enzyme, { configure, shallow, mount, render } from 'enzyme';
    import Adapter from 'enzyme-adapter-react-16';
    
    configure({ adapter: new Adapter() });
    export { shallow, mount, render };
    export default Enzyme;

    Don't worry about the named exports from enzyme here. We will get to play with them later.

    Finally, create a components and components/__tests__ folder inside src where our components and tests will live in respectively.

    Shallow Rendering

    Shallow rendering is the most basic version of testing with Enzyme. As the name suggests, shallow rendering limits it's scope to the component to be tested and not it's children.

    This comes in handy in various scenarios:

    1. For presentational/dummy components that simply render props, there is no need to try and render any other children.
    2. For components with deeply nested children components, a change in behavior of the children should not affect the behavior of the parent component to be tested.

    For this section, we will demonstrate testing a presentational component with shallow render.

    Take a look at the List component below that expects an items prop and displays them in an unordered list.

    src/components/List.js

    import React from 'react';
    import PropTypes from 'prop-types';
    
    /**
     * Render a list of items
     *
     * @param {Object} props - List of items
     */
    function List(props) {
      const { items } = props;
      if (!items.length) {
        // No Items on the list, render an empty message
        return <span className="empty-message">No items in list</span>;
      }
    
      return (
        <ul className="list-items">
          {items.map(item => <li key={item} className="item">{item}</li>)}
        </ul>
      );
    }
    
    List.propTypes = {
      items: PropTypes.array,
    };
    
    List.defaultProps = {
      items: [],
    };
    
    export default List;

    Let's add a few tests for the component.

    /src/components/tests/List.test.js

    import React from 'react';
    import { shallow } from '../enzyme';
    
    import List from './List';
    
    describe('List tests', () => {
    
      it('renders list-items', () => {
        const items = ['one', 'two', 'three'];
        const wrapper = shallow(<List items={items} />);
    
        // Expect the wrapper object to be defined
        expect(wrapper.find('.list-items')).toBeDefined();
        expect(wrapper.find('.item')).toHaveLength(items.length);
      });
    
      it('renders a list item', () => {
        const items = ['Thor', 'Loki'];
        const wrapper = shallow(<List items={items} />);
    
        // Check if an element in the Component exists
        expect(wrapper.contains(<li key='Thor' className="item">Thor</li >)).toBeTruthy();
      });
    
      it('renders correct text in item', () => {
        const items = ['John', 'James', 'Luke'];
        const wrapper = shallow(<List items={items} />);
    
        //Expect the child of the first item to be an array
        expect(wrapper.find('.item').get(0).props.children).toEqual('John');
      });
    });

    The test suite imports a shallow enzyme method from the configuration we created in the previous section, wraps the List component and returns an instance of the rendered component.

    We then make a series of assertions against the instance to check if the component renders the content correctly. While the tests are not optimal, we take advantage of a few methods exposed by the shallow API.

    Full DOM rendering

    in the last section, we were able to shallow render the List component and write tests to assert the actual text in the li tags. Sweet, right? In this section, we will look at full DOM rendering by making a few modifications to the component by breaking out the li element into a component of it's own called ListItem.

    src/compoents/ListItem.js

    import React from 'react';
    import PropTypes from 'prop-types';
    
    /**
     * Render a single item
     *
     * @param {Object} props
     */
    function ListItem(props) {
      const { item } = props;
      return <li className="item">{item}</li>;
    }
    
    ListItem.propTypes = {
      item: PropTypes.string,
    };
    
    export default ListItem;

    With the new component, replace the li tag with our shiny new component.

    src/components/List.js

    ...
    import ListItem from './ListItem';
    ...
    
    return (
        <ul className="list-items">
          {items.map(item => <ListItem key={item} item={item} />)}
        </ul>
      );

    Let's now run the tests we wrote in the previous section and see what happens. If you did this right, your tests should be failing as terribly as mine are.

    Failing tests

    Why would this happen? I mean the UI did not change at all. All we did was move things a littlte. Let's debug this further. The enzyme wrapper exposes a debug method that allows use to peek into the wrapped instance of our component and see what went wrong.

    Let's add a log in our tests to see what went wrong.

    /src/components/tests/List.test.js

    ...
    
    it('renders list-items', () => {
        const items = ['one', 'two', 'three'];
        const wrapper = shallow(<List items={items} />);
    
        // Let's check what wrong in our instance
        console.log(wrapper.debug());
    
        // Expect the wrapper object to be defined
        expect(wrapper.find('.list-items')).toBeDefined();
        expect(wrapper.find('.item')).toHaveLength(items.length);
      });
    
    ...

    Run the tests again and look through the terminal output, you should see our component instance log.

    Debug Log

    As you can see, the wrapper method does not render the ListItem Children as we would have expected. Therefore, our tests that checked for a class or li element failed.

    It may not seem necessary to shallow render such a simple component where the child is a presentational component but it comes in handy when you are writting tests for components that are wrapped by libraries such as react-redux's connect or reduxForm.

    The idea here is that we do not want to test the inner workings of such high order components, therefore, no need to concern ourselves with their rendering.

    Okay enough chatter. Let's fix the failling tests. We could stop checking for the li elements in our tests and check for the ListItem tag as shown below

    /src/components/tests/List.test.js

    ...
    
    it('renders list-items', () => {
        const items = ['one', 'two', 'three'];
        const wrapper = shallow(<List items={items} />);
    
        // Expect the wrapper object to be defined
        expect(wrapper.find('ListItem')).toBeDefined();
        expect(wrapper.find('ListItem')).toHaveLength(items.length);
      });
    
    ...
    

    In this case, we actually want to test the entire tree of children in the List component. so instead, we will replace the shallow component with mount. Mount enables us to perform a full render. Here is a snippet of the updated code and a log of the debug instance.

    /src/components/tests/List.test.js

    import React from 'react';
    import { mount } from '../enzyme';
    
    import List from './List';
    
    describe('List tests', () => {
    
      it('renders list-items', () => {
        const items = ['one', 'two', 'three'];
    
        // Replace shallow iwth mount
        const wrapper = mount(<List items={items} />);
    
        // Let's check what wrong in our instance
        console.log(wrapper.debug());
    
        // Expect the wrapper object to be defined
        expect(wrapper.find('.list-items')).toBeDefined();
        expect(wrapper.find('.item')).toHaveLength(items.length);
      });
    
      ...
    });

    Full render debug

    As you can see, mount's full rendering API renders the entire DOM, including that of the children. And our tests are fixed!

    Note: unlike shallow or static rendering, full rendering actually mounts the component in the DOM, which means that tests can affect each other if they are all using the same DOM. Keep that in mind while writing your tests and, if necessary, use .unmount() or something similar as cleanup.

    Enzyme Documentation

    Static Rendering

    Statics rendering works in the same way as shallow and mount but instead of returning an instance of the rendered output, it returns the rendered HMTL. It is built on top of Cheerio, a DOM manipulation and traversal API that borrows the magic of jQuery and strips out everything that we hate about it.

    For static rendering, you do not have access to Enzyme API methods such as contains and debug. You however have access to the full arsenal of Cheerios manipulation and traversal methods such as addClass and find respectively.

    To statically render a React component, import the render method as shown in the snippet below.

    /src/components/tests/List.test.js

    import React from 'react';
    import { render } from '../enzyme';
    
    import List from './List';
    import { wrap } from 'module';
    
    describe('List tests', () => {
    
      it('renders list-items', () => {
        const items = ['one', 'two', 'three'];
        const wrapper = render(<List items={items} />);
        wrapper.addClass('foo');
        // Expect the wrapper object to be defined
        expect(wrapper.find('.list-items')).toBeDefined();
        expect(wrapper.find('.item')).toHaveLength(items.length);
      });
    
      ...
    });

    Conclusion

    In this article, we have been able to go through the different ways of using Jest and Enzyme to test React components. While we did not dive into the specific methods exposed by the different renders, we were able to appreciate Enzyme's offerings.

    The Enzyme documentation is very well done and should be easy for you to take advantage of the different methods. Next we will take a look at testing events, props and state in React. Get testing!

    John Kariuki

    24 posts

    Software developer at Andela.

    PHP, JavaScript developer (AngularJS, React, Node.JS)

    Avid blog reader and fascinated by drones.

    I play basketball, swim and jog in my free time.