Testing of React components using Jest and Enzyme

Testing of React components using Jest and Enzyme

It can be either easy-but-useless or too hard to do write tests for React components. A good way to do it is to go for the middle using Jest - a test runner, Enzyme - a testing utility for React, and enzyme-to-json to convert Enzyme wrappers for Jest snapshot matcher. It means writing tests that do shallow rendering with Jest snapshots.

Shallow rendering

Shallow rendering renders only components themselves without child components. So changing something in a child component will not change the shallow output of the parent component. A bug introduced in a child component will not break the test of the parent.

For example this component:

const ButtonWithIcon = ({icon, children}) => (
  <button><Icon icon={icon} />{children}</button>
);

..will be rendered by React like this:

<button>
  <i class="icon icon_abc"></i>
  Hello!
</button>

..but with shallow rendering, like this (the icon component is not rendered):

<button>
  <Icon icon="abc" />
  Hello!
</button>

Snapshot testing

Jest snapshots are stored as text. The idea is that you want to be sure that output of this component should never change accidentally, so Jest saves it to a file that looks like this:

exports[`test should render a label 1`] = `
<label
  className="isBlock">
  Hello!
</label>
`;

exports[`test should render a small label 1`] = `
<label
  className="isBlock isSmall">
  Hello!
</label>
`;

Every time you change your component Jest will show there is a diff and you can then determine if you have a bug to fix of if it's just the snapshot that needs to be updated.

Jest stores the snapshot files next to your tests in a __snapshots__ dir with file names like AddNewDestinationButton.test.jsx.snap and you should commit them along with your code.

Reasons to use Jest

  • Allows for snapshot testing
  • Has interactive watch mode that reruns only tests that are relevant to your changes
  • Helpful failure messages
  • Simple configuration
  • Mocks and spies
  • Coverage report with a single command line switch.

Reasons to use Enzyme

  • Convenient utilities to work with shallow rendering
  • jQuery-like API to find elements, read props, etc

Setting up

First install all the dependencies including peer dependencies:

npm install --save-dev jest babel-jest react-addons-test-utils \ 
  enzyme enzyme-to-json

Update your package.json:

"scripts": {
  "test": "jest",
  "test:watch": "jest --watch",
  "test:coverage": "jest --coverage"
},
"jest": {
  "setupFiles": ["./test/jestsetup.js"],
  "snapshotSerializers": [
    "<rootDir>/node_modules/enzyme-to-json/serializer"
  ]
}

"snapshotSerializers" allows you to pass Enzyme wrappers directly to Jest's snapshot matcher without converting them manually by calling enzyme-to-json's toJson function.

Create a jestsetup.js file to customize Jest environment ("setupFiles" above):

// Make Enzyme functions available in all test files without importing
import { shallow, render, mount } from 'enzyme';
global.shallow = shallow;
global.render = render;
global.mount = mount;
// Fail tests on any warning
console.error = message => {
   throw new Error(message);
};

Writing tests

Testing basic component rendering

This is usually enough for non-interactive components:

it('should render a label', () => {
  const wrapper = shallow(
    <Label>Hello!</Label>
  );
  expect(wrapper).toMatchSnapshot();
});

it('should render a small label', () => {
  const wrapper = shallow(
    <Label small>Hello!</Label>
  );
  expect(wrapper).toMatchSnapshot();
});

it('should render a grayish label', () => {
  const wrapper = shallow(
    <Label light>Hello!</Label>
  );
  expect(wrapper).toMatchSnapshot();
});

Testing props

Sometimes you want to be more explicit and see real values in tests. In that case use Enzyme API with Jest assertions:

it('should render a document title', () => {
  const wrapper = shallow(
    <DocumentTitle title="Events" />
  );
  expect(wrapper.prop('title')).toEqual('Events');
});

it('should render a document title and a parent title', () => {
  const wrapper = shallow(
    <DocumentTitle title="Events" parent="Event Radar" />
  );
  expect(wrapper.prop('title')).toEqual('Events — Event Radar');
});

In some cases you just can't use snapshots. For example if you have random IDs or something like that.

Testing events

You can simulate an event like click or change and then compare component to a snapshot:

it('should render Markdown in preview mode', () => {
  const wrapper = shallow(
    <MarkdownEditor value="*Hello* there!" />
  );

  expect(wrapper).toMatchSnapshot();

  wrapper.find('[name="toggle-preview"]').simulate('click');

  expect(wrapper).toMatchSnapshot();
});

Testing event handlers

Similar to events testing but instead of testing component's rendered output with a snapshot use Jest's mock function to test an event handler itself:

it('should pass a selected value to the onChange handler', () => {
  const value = '2';
  const onChange = jest.fn();
  const wrapper = shallow(
    <Select items={ITEMS} onChange={onChange} />
  );

  expect(wrapper).toMatchSnapshot();

  wrapper.find('select').simulate('change', {
    target: { value },
  });

  expect(onChange).toBeCalledWith(value);
});

Jest snapshots work with JSON so you can test any function that returns JSON, - not only JSX.


To view or add a comment, sign in

More articles by Johan Brattemark

  • Making my first real Web App

    Having had a long detour from web development and deciding to come back into it, I signed up for a six-month full-time…

    1 Comment
  • Localizing a client-side app

    Localization (L10n) is the process of adapting your app to users with a different language and various settings like…

  • 15 years away from web development

    Being a Galvanize student at the Web Development full-time program, making web applications the newest way, I found…

Others also viewed

Explore content categories