Writing Snapshot Tests For React Components With Jest

John Kariuki
👁️ 586 views
💬 comments

In this tutorial, we will be looking at what snapshot tests are and how we can use snapshot testing to ensure our User Interface does not change without the team knowing about it.

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

  1. NodeJS - A JavaScript runtime built on Chrome's V8 JavaScript engine.
  2. React - A JavaScript library for building delightful UI by Facebook
  3. Jest - A JavaScript testing framework by Facebook.

What Is Snapshot Testing?

Unlike strict Test Driven Development where the standard practice is to write failing tests first then write the code to make the tests pass, Snapshot testing takes a different approach.

Table of Contents

    To write a snapshot test, you first get your code working, say, a React component, then generate a snapshot of it's expected output given certain data. The snapshot tests are commited alongside the component and everytime the tests are run. Jest will compare the snapshot to the rendered output for the test.

    If the test does not pass, it may mean that there were some unexpected changes on the component that you need to fix, or you made some changes to the component and it's about time you updated the snapshot tests.

    Snapshot testing is meant to be one of many different testing tools. Therefore, you may still need to write tests for your actions and reducers.

    Let's get right into it!

    Creating a Simple React Component

    To get started, we will create a simple React App using Create React App.

    create-react-app ooh-snap
    cd ooh-snap
    yarn start

    We should now have a React app! Let's go ahead and create a component that we can test. The component that we are going to be creating renders the items props it receives as either a list or as a span element depending on the number of items.

    Create a Components folder then add the following Items component

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

    Finally, let's update App.js to render our Component

    import React, { Component } from 'react';
    import Items from './Components/Items';
    
    class App extends Component {
      render() {
        const items = [
          'Thor',
          'Captain America',
          'Hulk'
        ];
        return (
          <Items items={items} />
        );
      }
    }
    
    export default App;

    Effectively, delete App.test.js because we will be adding our own tests in the next section.

    Simple, right? Next, let's go ahead and add our snapshot tests

    Writing Snapshot Tests

    To get started, install react-test-renderer, a library that enables you to render React components as JavaScript objects without the need of a DOM.

    yarn add react-test-renderer

    Great, let's add our first test. To get started, we will create a test that renders the Items component with no items passed down as props.

    import React from 'react';
    import renderer from 'react-test-renderer';
    
    import Items from './Items';
    
    it('renders correctly when there are no items', () => {
      const tree = renderer.create(<Items />).toJSON();
      expect(tree).toMatchSnapshot();
    });

    Next, let's run the tests. Thanks to Create React App, we do not need to set anything else up to run our tests.

    yarn test

    When your run the tests for the first time, notice that a new snapshot file is created inside a __snapshots__ directory. Since our test file is named Items.test.js, the snapshot file is appropriately named Items.test.js.snap that looks like this.

    // Jest Snapshot v1, https://goo.gl/fbAQLP
    
    exports[`renders correctly when there are no items 1`] = `
    <span>
      No items in list
    </span>
    `;

    Simple, right? The test matches the component's exact output. Jest uses pretty-format to make the snapshot files human readable.

    If you are getting the hang of it, go ahead and create tests for the two other scenarios where there is one item and where there is multiple items, then run the tests.

    ...
    it('renders correctly when there is one item', () => {
      const items = ['one'];
      const tree = renderer.create(<Items items={items} />).toJSON();
      expect(tree).toMatchSnapshot();
    });
    
    it('renders correctly when there are multiple items', () => {
      const items = ['one', 'two', 'three'];
      const tree = renderer.create(<Items items={items} />).toJSON();
      expect(tree).toMatchSnapshot();
    });

    Next, let's make updates to our component.

    Updating Snapshot Tests

    To understand why we need snapshot tests, we'll go ahead and update the Items component and re-run the tests. This, for your dev environment, is a simulation of what would happen when someone on your team makes a change to a component and your CI tool runs the tests.

    We will add class names to the span and li elements, say, to effect some styling.

    ...
    /**
     * Render a list of items
     *
     * @param {Object} props - List of items
     */
    function Items(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>;
      }
    
      if (items.length === 1) {
        // One Item in the list, render a span
        return <span className="item-message">{items[0]}</span>;
      }
    
      // Multiple items on the list, render a list
      return (
        <ul>
          {items.map(item => <li key={item} className="item-message">{item}</li>)}
        </ul>
      );
    }
    ...

    Let's run our tests again with yarn test command in lieu of our changes. Notice anything different?

    Failing tests

    That's not good, is it? Jest matched the existing snapshots against the rendered component with the updated changes and failed because there were some additions to our compnent. It then shows a diff of the changes that are introduced to the snapshot tests.

    To fix this, for whatever reason, would entirely depend on the changes that were introduced to the snapshot tests.

    If the changes are not expected, that's good, you got it well in advance before it was too late. If the changes were expected, update your snapshot tests and everything is green again.

    While Jest is in interactive mode, you can update the snapshot tests by simply pressing u with the options provided. alternatively, you can run jest --updateSnapshot or jest -u.

    Watch Mode Options

    This will update the snapshots to match the updates we made and our tests will effectively pass. Passing tests

    Go ahead and pick into the snapshots folder to see how the snapshot files changed. Here is the updated empty snapshot snippet.

    exports[`renders correctly when there is one item 1`] = `
    <span
      className="item-message"
    >
      one
    </span>
    `;

    Conclusion

    In this tutorial, we have been able to write snapshot tests for a React component. We also updated the component to see the failing tests and eventually update the snapshots to fix the tests.

    I hope you can now appreciate how easy it is to iterate and debug your UI changes especially when working as part of a big team.

    While we have covered the basics of snapshot tests, there is a lot you could learn on writting better snapshot tests. Do take a look at the Snapshot best practices from the Jest's documentation to learn more about snapshot testing.

    John Kariuki

    23 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.