Blog

Ideas and insights from our team

Unit testing React Components with Jest


Jest is a JavaScript testing framework, it's fast and has an awesome snapshot testing feature.

This post is not an introduction to Jest, there are plenty of those around. In this post we will show how to unit test your components in an isolated manner.

If you want to follow along create a project with react-create-app, it comes with Jest. To add to your project see Getting Started.

The complete example used on this post here.

Let's start with a simple React component:

// components/ArticleTitle.js
import React from 'react';

const ArticleTitle = ({ title, url, timestamp }) => (
  <div>
    <h1>{title} [{timestamp}]</h1>
    <h4><a href={url}>{url}</a></h4>
  </div>
);

To test this component in an isolated manner we can pass the props it receives title, url, timestamp. And check if the output is what we expected. One way we could do this is to check if each element (h1, h4, a) are rendered correctly. Another way to do it is by using snapshot. A snapshot is a representation of the current state of the UI of your component.

In Jest, snapshots are generated automatically the first time we compare a rendered component to a snapshot. Say we want to create a snapshot of component ArticleTitle. All we have to do is create an instance of ArticleTitle and ask Jest to check if it matches its snapshot. Since ArticleTitle has no snapshot it will create one.

Let's go ahead and create a test for our ArticleTitle component.

// components/ArticleTitle.test.js
import renderer from 'react-test-renderer';
import React from 'react';
import ArticleTitle from './ArticleTitle';

test('ArticleTitleTest renders correctly', () => {
  const tree = renderer.create(
    <ArticleTitle title="A title" url="http://example.com" timestamp="2015-12-31" />
  ).toJSON();
  expect(tree).toMatchSnapshot();
});

If you create a project with react-create-app you can run the test with:

npm run test

If we run our tests with Jest you will see the following message: 1 snapshot written in 1 test suite..

// components/__snapshots__/ArticleTitle.test.js.snap
exports[`test ArticleTitleTest renders correctly 1`] = `
<div>
  <h1>
    A title
     [
    2015-12-31
    ]
  </h1>
  <h4>
    <a
      href="http://example.com">
      http://example.com
    </a>
  </h4>
</div>
`;

Now, every time the test runs it will compare with the snapshot and when the new snapshot is different, the test will fail.

NOTE: this file should be kept in your VCS (ie: git).

Let's see how to deal with components that have other components.

// components/Article.js
const Article = ({ article }) => (
  <div>
    <ArticleTitle title={article.title} timestamp={article.timestamp} url={article.url}/>
    <ArticleBody body={article.body} />
  </div>
);

We want to be able to isolate the components in a way that the test for Article only tests Article. If we change the code of ArticleTitle we don't want the test for Article to break.

To do this, we create a mock. First create a folder called __mocks__ in the same folder as ArticleTitle.js. Inside the mocks folder create a file with the same name as the one to be mocked ArticleTitle.js.

// components/__mocks__/ArticleTitle.js
import React from 'react';

const ArticleTitle = props => (
  <span>ArticleTitle {JSON.stringify(props)}</span>
);

A trick we use here is to convert the props to JSON. This will only work if your component props are JSON serializable. This is useful because we test that Article is sending the right props to ArticleTitle. What ArticleTitle does with it is not a concern of this test.

Here is what the test and the snapshot for Article.test.js will look like:

// components/Article.test.js
import renderer from 'react-test-renderer';
import React from 'react';
import Article from './Article';

// Tell we are mocking ArticleTitle and ArticleBody
jest.mock('./ArticleTitle.js');
jest.mock('./ArticleBody.js');

const article = {
  timestamp: '2016-11-12',
  body: 'article body',
  url: 'http://example.com',
  title: 'An Article',
}

test('renders correctly', () => {
  const tree = renderer.create(
    <Article article={article} />
  ).toJSON();
  expect(tree).toMatchSnapshot();
});
// components/__snapshots__/ArticleTitle.test.js.snap
exports[`test renders correctly 1`] = `
<div>
  <span>
    ArticleTitle
    {\"title\":\"An Article\",\"timestamp\":\"2016-11-12\",\"url\":\"http://example.com\"}
  </span>
  <span>
    ArticleBody
    {\"body\":\"article body\"}
  </span>
</div>
`;

You can also interact with the component and generate multiple snapshots. Here are a more advanced component and its tests that will generate multiple snapshots. When the mouse goes over the component it adds a class and remove it once the mouse is outside the component.

// components/Article.js
import React from 'react';
import { bindAll } from 'lodash';
import ArticleTitle from './ArticleTitle';
import ArticleBody from './ArticleBody';

class Article extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isMouseOver: false,
    }
    bindAll(this, ['onMouseEnter', 'onMouseLeave']);
  }

  onMouseEnter() {
    this.setState({isMouseOver: true});
  }

  onMouseLeave() {
    this.setState({isMouseOver: false});
  }

  render () {
    const { article } = this.props;
    return (
      <div
        style={{display: 'inline-block'}} className={this.state.isMouseOver ? 'has-mouse-over' : ''}
        onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}
      >
        <ArticleTitle title={article.title} timestamp={article.timestamp} url={article.url}/>
        <ArticleBody body={article.body} />
      </div>
    );
  }
}
// components/Article.test.js
import renderer from 'react-test-renderer';
import React from 'react';
import Article from './Article';

jest.mock('./ArticleTitle.js');
jest.mock('./ArticleBody.js');

const article = {
  timestamp: '2016-11-12',
  body: 'article body',
  url: 'http://example.com',
  title: 'An Article',
}

test('renders correctly', () => {
  const component = renderer.create(
    <Article article={article} />
  );

  let tree = component.toJSON();
  expect(tree).toMatchSnapshot();

  // manually trigger the callback
  tree.props.onMouseEnter();
  // re-rendering
  tree = component.toJSON();
  expect(tree).toMatchSnapshot();

  // manually trigger the callback1
  tree.props.onMouseLeave();
  // re-rendering
  tree = component.toJSON();
  expect(tree).toMatchSnapshot();
});

Summing up

  • You can see the full example here.
  • Jest is a powerful tool to unit test React components. Run it once and it will make sure that every time you run your tests it creates the same component as the first time.
  • Jest uses Jasmine's assertions, see the API here.
  • You should isolate components. By doing this each test will only cover a specific component.
  • Jest snapshots is just one tool to help you write test, it's not a replacement for enzyme. You will probably still want to use Enzyme.
  • Always carefully check the diff when a snapshot test fail before updating the snapshot.

More from Vinta
Check out our Lessons Learned page and subscribe the our newsletter
JavaScript's Lambda and Arrow Functions

About André Ericson

Frontend and Backend developer, Python and modern JavaScript evangelist. CLI is my /home/.

Comments