Testing React with Jest and Enzyme I

This post will look at how to setup and use Jest and Enzyme to test a React application created with Create React App (CRA). Some pointers will be given for those working from scratch. Basic knowledge of React is assumed.

Jest and Enzyme are different but complimentary tools, that integrate well together to provide flexible and creative testing abilities. We will briefly look at the differences between the two.

Jest

Jest is a JavaScript unit testing framework, used by Facebook to test services and React applications.

CRA comes bundled with Jest; it does not need to be installed separately.

Jest acts as a test runner, assertion library, and mocking library.

Jest also provides Snapshot testing, the ability to create a rendered ‘snapshot’ of a component and compare it to a previously saved ‘snapshot’. The test will fail if the two do not match. Snapshots will be saved for you beside the test file that created them in an auto-generate __snapshots__ folder. An example snapshot could be as simple as:

exports[`List shallow renders correctly with no props 1`] = `
<List
ordered={false}
>
<ListItem>
Item 1
</ListItem>
<ListItem>
Item 1
</ListItem>
</List>
`;

Snapshot testing must be complimented with in browser testing and looking at the snapshot created when creating the initial snapshots, to ensure that the snapshot reflects the intended outcome.

Enzyme

Enzyme is a JavaScript Testing utility for React that makes it easier to assert, manipulate, and traverse your React Components’ output.

Enzyme, created by Airbnb, adds some great additional utility methods for rendering a component (or multiple components), finding elements, and interacting with elements.

It must be installed in addition to tools already bundled with CRA.

Jest and Enzyme

  • Both Jest and Enzyme are specifically designed to test React applications, Jest can be used with any other Javascript app but Enzyme only works with React.
  • Jest can be used without Enzyme to render components and test with snapshots, Enzyme simply adds additional functionality.
  • Enzyme can be used without Jest, however Enzyme must be paired with another test runner if Jest is not used.

As described, we will use:

  • Jest as the test runner, assertion library, and mocking library
  • Enzyme to provide additional testing utilities to interact with elements

Setup

Installing and configuring

If not using CRA install Jest:

npm install --save-dev jest babel-jest

Install Enzyme:

npm install --save-dev enzyme enzyme-adapter-react-16 enzyme-to-json

Update your package.json :

"jest": {
"snapshotSerializers": ["enzyme-to-json/serializer"]
}

enzyme-to-json provides a better component format for snapshot comparison than Enzyme’s internal component representation. snapshotSerializers allows you to minimise code duplication when working with snapshots. Without the serializer each time a component is created in a test it must have the enzyme-to-json method .toJson() used individually before it can be passed to Jest’s snapshot matcher, with the serializer you never use it individually.

expect(toJson(rawRenderedComponent)).toMatchSnapshot();

With this additional line in package.json it allows you to pass a component created by Enzyme to the Jest .toMatchSnapshot() without calling this interim JSON method.

Create a setupTests.js file at ./src/setupTests.js:

import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });

CRA will automatically pick up this file, if not using CRA then also add this line in the same location as snapshotSerializers above:

"setupFiles": ["./src/setupTests.js"],

Creating a test file

Jest will look for tests in any of the following places:

  • Files with .js suffix in __tests__ folders.
  • Files with .test.js suffix.
  • Files with .spec.js suffix.

It is convention to put each test file next to the code it is testing. This makes semantic sense, and also means relative paths are shorter ( ./MyComponent vs ../../MyComponent etc).

An example of this could be this MyComponent.test.jsfile:

import React from 'react';
import { shallow } from 'enzyme';
import MyComponent from './MyComponent';
describe('MyComponent', () => {
it('should render correctly in "debug" mode', () => {
    const component = shallow(<MyComponent debug />);

expect(component).toMatchSnapshot();
});
});

When npm test in CRA is ran it will run all test files and output the results to the terminal. Customisation flags exist to run against specific files only, or conversely ignore specific files using -- --testPathPattern filename/ and -- --testPathIgnorePatterns filename/.

Mount, Shallow, Render

import { mount, shallow, render } from ‘enzyme';

In order to have a component to test one of the above must be used, as in the example further above.

Mounting

  • Full DOM rendering including child components
  • Ideal for use cases where you have components that may interact with DOM API, or use React lifecycle methods in order to fully test the component
  • As it actually mounts the component in the DOM .unmount() should be called after each tests to stop tests affecting each other
  • Allows access to both props directly passed into the root component (including default props) and props passed into child components

Shallow

  • Renders only the single component, not including its children. This is useful to isolate the component for pure unit testing. It protects against changes or bugs in a child component altering the behaviour or output of the component under test
  • As of Enzyme 3 shallow components do have access to lifecycle methods by default
  • Cannot access props passed into the root component (therefore also not default props), but can those passed into child components, and can test the effect of props passed into the root component. This is as with shallow(<MyComponent />), you're testing what MyComponent renders - not the element you passed into shallow

Render

  • Renders to static HTML, including children
  • Does not have access to React lifecycle methods
  • Less costly than mount but provides less functionality

Testing

Basic component rendering

For simple non-interactive components:

it('should render correctly with no props', () => {
const component = shallow(<MyComponent/>);

expect(component).toMatchSnapshot();
});
it('should render banner text correctly with given strings', () => {
const strings = ['one', 'two'];
  const component = shallow(<MyComponent list={strings} />);
  expect(component).toMatchSnapshot();
});

Events

The Enzyme API has several ways to simulate events or user interactions. If you are wanting to test interacting with a child component then the mount method can be used.

it('should be possible to activate button with Spacebar', () => {
const component = mount(<MyComponent />);
  component
.find('button#my-button-one')
.simulate('keydown', { keyCode: 32 });
  expect(component).toMatchSnapshot();
component.unmount();
});

Mock functions

You may simply want to check that a function passed as props is successfully called.

const clickFn = jest.fn();
describe('MyComponent', () => {
it('button click should hide component', () => {
const component = shallow(<MyComponent onClick={clickFn} />);
    component
.find('button#my-button-two')
.simulate('click');
    expect(clickFn).toHaveBeenCalled();
});
});

Getting more complex, you may wish to mock a function imported and used within MyComponent.js, set its return value, check it is called, and compare its snapshot.

Lets imagine that within MyComponent.js we
import { SaveToStorage } from 'save-to-storage'
before creating a new SaveToStorage object, which has both TryGetValue and TrySetValue methods. TryGetValue has a default return value of false, if it returns true the component will change. Our component uses these within different button clicks.

We can use jest.mock to mock this, as well as jest.fn to provide overrides for the functions within it.

const mockTryGetValue = jest.fn(() => false);
const mockTrySetValue = jest.fn();

jest.mock('save-to-storage', () => ({
SaveToStorage: jest.fn().mockImplementation(() => ({
tryGetValue: mockTryGetValue,
trySetValue: mockTrySetValue,
})),
}));
describe('MyComponent', () => {
it('should set storage on save button click', () => {
mockTryGetValue.mockReturnValueOnce(true);
    const component = mount(<MyComponent />); 
    component.find('button#my-button-three').simulate('click');
    expect(mockTryGetValue).toHaveBeenCalled();
expect(component).toMatchSnapshot();
component.unmount();
});
});

Conclusion

These are just some of the methods available, Jest has several other assertion methods available (including toEqual), and Enzyme many more traversal and interactive methods available (including first and setProps). The documentation for each is good, and opens up many new possibilities.

Using Jest and Enzyme together makes testing React components much easier, and makes for very readable tests.

Thanks for reading! 🙂

If you liked this, you might also like:

Resources

In addition to the official documentation and hacking around on my own, I found the following two articles particularly helpful, and in helping to cement my knowledge by writing this post you will clearly see influence from them:

If you are also wishing to look at visual regression testing in addition to unit and integration testing I can recommend looking to Differencify.