Testing React

Part 2, Lesson 5



Let's look at testing React components...


Create React App uses Jest, a JavaScript test runner, by default, so we can start writing test specs. You will need to install the jest-cli - in the "client" folder - to run the actual tests, though:

$ npm install [email protected] --save-dev

Along with Jest, we'll use Enzyme, a fantastic utility library made specifically for testing React components.

Install it as well enzyme-adapter-react-16:

$ npm install --save-dev [email protected] [email protected]

To configure the Enzyme to use the React 16 adapter, add a new file to "src" called setupTests.js:

import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

For more on setting up Enzyme, review the official docs.

With that, run the tests:

$ npm test

You should see:

No tests found related to files changed since last commit.

By default, the tests run in watch mode, so the tests will re-run every time you save a file.

Testing Components

Create a new file in "components" called UsersList.test.js:

import React from 'react';
import { shallow } from 'enzyme';

import UsersList from '../UsersList';

const users = [
  {
    'active': true,
    'email': '[email protected]',
    'id': 1,
    'username': 'michael'
  },
  {
    'active': true,
    'email': '[email protected]',
    'id': 2,
    'username': 'michaelherman'
  }
]

test('UsersList renders properly', () => {
  const wrapper = shallow(<UsersList users={users}/>);
  const element = wrapper.find('h4');
  expect(element.length).toBe(2);
  expect(element.get(0).props.className).toBe('well');
  expect(element.get(0).props.children).toBe('michael');
});

In this test, we use the shallow helper method to create the UsersList component and then we can retrieve the output and make assertions on it. It's important to note that with "shallow rendering", we can test the component in complete isolation, which helps to ensure child components do not indirectly affect assertions.

For more on shallow rendering, along with the other methods of rendering components for testing (mount and render) see this Stack Overflow article.

Run the test to ensure it passes.

Snapshot Testing

Once the test is green, we'll then add a Snapshot test to ensure the UI does not change.

Add the following test to UsersList.test.js:

test('UsersList renders a snapshot properly', () => {
  const tree = renderer.create(<UsersList users={users}/>).toJSON();
  expect(tree).toMatchSnapshot();
});

Add the import to the top:

import renderer from 'react-test-renderer';

Run the tests. So, on the first test run, a snapshot is saved of the component output (to the "__snapshots__" folder). Then, during subsequent test runs, the new output is compared to the saved output. The test fails if they differ.

With the tests in watch mode, change {user.username} to {user.email} in the UsersList component. Save the changes to trigger a new test run. You should see both tests failing, which is exactly what we want. Now, if this change is intentional, you need to update the snapshot. To do so, you just need to press the u key:

Watch Usage
 › Press a to run all tests.
 › Press u to update failing snapshots.
 › Press p to filter by a filename regex pattern.
 › Press t to filter by a test name regex pattern.
 › Press q to quit watch mode.
 › Press Enter to trigger a test run.

Try it out - press u. The tests will run again and the snapshot test should pass.

Once done, revert the changes we just made in the component, update the tests, ensure they pass, add the __snapshots__ folder to the .gitignore file, and then commit your code.

Test Coverage

Curious about test coverage?

$ react-scripts test --coverage

Testing Interactions

Enzyme can also be used to test user interactions in terms of events. We can simulate such actions and events and then test that the actual results are the same as the expected results. We'll look at this in a future lesson.

It's worth noting that we'll focus much of our React testing on unit testing the individual components. We'll let the end-to-end tests handle testing user interaction as well as the interaction between the client and server.

requestAnimationFrame polyfill error

Do you get this error when your tests run?

console.error node_modules/fbjs/lib/warning.js:33
    Warning: React depends on requestAnimationFrame. Make sure that you load a polyfill in older browsers. http://fb.me/react-polyfills

If so, add a new folder to "services/client/src/components" called "__mocks__", and then add a file to that folder called react.js:

const react = require('react');
// Resolution for requestAnimationFrame not supported in jest error :
// https://github.com/facebook/react/issues/9102#issuecomment-283873039
global.window = global;
window.addEventListener = () => {};
window.requestAnimationFrame = () => {
  throw new Error('requestAnimationFrame is not supported in Node');
};

module.exports = react;

Review the comment on GitHub for more info.


Testing React

Let's look at testing React components...


Create React App uses Jest, a JavaScript test runner, by default, so we can start writing test specs. You will need to install the jest-cli - in the "client" folder - to run the actual tests, though:

$ npm install [email protected] --save-dev

Along with Jest, we'll use Enzyme, a fantastic utility library made specifically for testing React components.

Install it as well enzyme-adapter-react-16:

$ npm install --save-dev [email protected] [email protected]

To configure the Enzyme to use the React 16 adapter, add a new file to "src" called setupTests.js:

import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

For more on setting up Enzyme, review the official docs.

With that, run the tests:

$ npm test

You should see:

No tests found related to files changed since last commit.

By default, the tests run in watch mode, so the tests will re-run every time you save a file.

Testing Components

Create a new file in "components" called UsersList.test.js:

import React from 'react';
import { shallow } from 'enzyme';

import UsersList from '../UsersList';

const users = [
  {
    'active': true,
    'email': '[email protected]',
    'id': 1,
    'username': 'michael'
  },
  {
    'active': true,
    'email': '[email protected]',
    'id': 2,
    'username': 'michaelherman'
  }
]

test('UsersList renders properly', () => {
  const wrapper = shallow(<UsersList users={users}/>);
  const element = wrapper.find('h4');
  expect(element.length).toBe(2);
  expect(element.get(0).props.className).toBe('well');
  expect(element.get(0).props.children).toBe('michael');
});

In this test, we use the shallow helper method to create the UsersList component and then we can retrieve the output and make assertions on it. It's important to note that with "shallow rendering", we can test the component in complete isolation, which helps to ensure child components do not indirectly affect assertions.

For more on shallow rendering, along with the other methods of rendering components for testing (mount and render) see this Stack Overflow article.

Run the test to ensure it passes.

Snapshot Testing

Once the test is green, we'll then add a Snapshot test to ensure the UI does not change.

Add the following test to UsersList.test.js:

test('UsersList renders a snapshot properly', () => {
  const tree = renderer.create(<UsersList users={users}/>).toJSON();
  expect(tree).toMatchSnapshot();
});

Add the import to the top:

import renderer from 'react-test-renderer';

Run the tests. So, on the first test run, a snapshot is saved of the component output (to the "__snapshots__" folder). Then, during subsequent test runs, the new output is compared to the saved output. The test fails if they differ.

With the tests in watch mode, change {user.username} to {user.email} in the UsersList component. Save the changes to trigger a new test run. You should see both tests failing, which is exactly what we want. Now, if this change is intentional, you need to update the snapshot. To do so, you just need to press the u key:

Watch Usage
 › Press a to run all tests.
 › Press u to update failing snapshots.
 › Press p to filter by a filename regex pattern.
 › Press t to filter by a test name regex pattern.
 › Press q to quit watch mode.
 › Press Enter to trigger a test run.

Try it out - press u. The tests will run again and the snapshot test should pass.

Once done, revert the changes we just made in the component, update the tests, ensure they pass, add the __snapshots__ folder to the .gitignore file, and then commit your code.

Test Coverage

Curious about test coverage?

$ react-scripts test --coverage

Testing Interactions

Enzyme can also be used to test user interactions in terms of events. We can simulate such actions and events and then test that the actual results are the same as the expected results. We'll look at this in a future lesson.

It's worth noting that we'll focus much of our React testing on unit testing the individual components. We'll let the end-to-end tests handle testing user interaction as well as the interaction between the client and server.

requestAnimationFrame polyfill error

Do you get this error when your tests run?

console.error node_modules/fbjs/lib/warning.js:33
    Warning: React depends on requestAnimationFrame. Make sure that you load a polyfill in older browsers. http://fb.me/react-polyfills

If so, add a new folder to "services/client/src/components" called "__mocks__", and then add a file to that folder called react.js:

const react = require('react');
// Resolution for requestAnimationFrame not supported in jest error :
// https://github.com/facebook/react/issues/9102#issuecomment-283873039
global.window = global;
window.addEventListener = () => {};
window.requestAnimationFrame = () => {
  throw new Error('requestAnimationFrame is not supported in Node');
};

module.exports = react;

Review the comment on GitHub for more info.