Testing React applications with jest, jest-axe, and react-testing-library

Dominic Fraser
7 min readApr 19, 2019

--

jest and react-testing-library logos

jest and react-testing-library are an increasingly used tooling pairing to test React components. We will briefly look at the differences between the two before looking at some high level examples, and how straightforward it is to add jest-axe to integrate aXe automated accessibility testing.

Jest

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

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. We will talk about the pros and cons of this later on.

Create React App comes bundled with Jest; it does not need to be installed separately.

React Testing Library

React Testing Library is a light-weight solution for testing React components. It provides utilities to facilitate querying the DOM in the same way the user would.

react-testing-library, created by Kent C. Dodds, adds utility methods for rendering a component (or multiple components), finding elements, and interacting with elements.

It is built on top of dom-testing-library and so includes all of its functionality.

It is possible, and encouraged, to be used alongside jest-dom. jest-dom is a companion library for react-testing-library that provides custom DOM element matchers for Jest such as toHaveTextContent or toBeVisible.

Both must be installed in addition to tools already bundled with Create React App.

Opinionated Testing Practices

react-testing-library is opinionated in its belief of good testing practices, and is a deliberate replacement for enzyme. react-testing-library‘s guiding principle is:

The more your tests resemble the way your software is used, the more confidence they can give you.

It believes that components should be tested by interacting with it as a user would, exposing several types of ByText queries to find elements by their text as a user would and so assert on what it finds.

It believes that enzyme‘s deeper API and shallow rendering abilities ‘encourage testing implementation details’ (such as State or internal method calls) which, as users are unaware of these details, produces false confidence in comparison to testing output and functionality.

Jest aXe

Custom Jest matcher for aXe for testing accessibility

Accessibility (a11y) of applications is much easier to get right if worked in from the start, rather than retroactively going back to try and fix. While the GDS Accessibility team found that only ~30% of issues are found by automated testing it can help to establish a baseline with virtually no effort to include and run.

It cannot and does not guarantee what we build is accessible, as it can only statically analyse code, but it provides a great start.

For full accessibility we’d also need to:

Jest and React Testing Library

  • Both Jest and React Testing Library are specifically designed to test React applications. Jest can be used with any other Javascript app but React Testing Library only works with React.
  • Jest can be used without React Testing Library to render components and test with snapshots, React Testing Library simply adds additional functionality.
  • React Testing Library can be used without Jest, however React Testing Library 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
  • React Testing Library to provide additional testing utilities to interact with elements
  • Jest DOM to extend Jest with useful custom DOM element matchers
  • Jest aXe for automated accessibility testing

Setup

Installing and configuring

If not using Create React App (CRA) install jest:

npm install --save-dev jest babel-jest

Install react-testing-library, jest-dom, and jest-axe:

npm install --save-dev react-testing-library jest-dom jest-axe

This guide was written using jest@23.6.0, react-testing-library@6.1.2 , jest-dom@3.1.3 , and jest-axe@3.1.1.

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

import 'jest-axe/extend-expect';import 'jest-dom/extend-expect';import 'react-testing-library/cleanup-after-each';

This allows for less code duplication in each test. Rather than importing the extend-expect‘s per test if put in the Jest-specific setupTests these are imported automatically before every test. In the same way cleanup-after-each calls afterEach(cleanup) after each test (used to help prevent memory leaks between tests) but only needs to be written once.

CRA will automatically pick up this file, if not using CRA then also add this line in the your package.json:

"jest": {
"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 { render } from 'react-testing-library';
import MyComponent from './MyComponent';describe('MyComponent', () => {
it('should render without crashing, () => {
const { container } = render(<MyComponent />);

expect(container).toHaveTextContent('Component Title');
});
});

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

Testing

Basic component rendering

As seen in the example above components are rendered into a container that can then have expectations used on it.

const { container } = render(<MyComponent />);expect(container).toHaveStyle('padding-top: 125px;');;

toHaveStyle is an example of a custom matcher from jest-dom. Others include toHaveTextContent and toBeVisible, with the full list seen in the jest-dom docs.

react-testing-library contains several query functions to find elements, which can be extracted when the component is rendered. These can be used to find elements and then perform actions on them, or to assert if elements are present based on if they can be found.

const { container, getByLabelText } = render(<MyComponent />);expect(getByLabelText('some exact text')).toBeTruthy;

This syntax is different to the dom-testing-library that react-testing-library is based on and extends, where a query would be imported once per file, but need the rendered component passed in as a target each time a query was used.

// alternate approach not used in docs, requiring additional install
import { getByLabelText } from 'dom-testing-library';
...const { container } = render(<MyComponent />);expect(getByLabelText(container, 'some exact test')).toBeTruthy();

A cheatsheet can be seen in react-testing-library‘s docs.

These query functions and matchers allow us to test the functionality of the component based on what the user would actually see, rather than the implementation details beneath it.

Events

fireEvent is used to simulate user interaction, with several events available.

const element = getByText('Submit');fireEvent.click(element);// or sayfireEvent.keyDown(element, { key: 'Enter', code: 13 });

Mock functions

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

const onClick = jest.fn();const { container } = render(<MyComponent onClick={onClick} />);const element = getByText('Submit');fireEvent.click(element);expect(onClick).toHaveBeenCalledTimes(1);

It is possible to use jest.fn().mockImplementation to mock the function in more detail if required.

Snapshot testing

Snapshot testing is a contentious topic. It is a fast way to test the full HTML output of a component, and so be able to see even the smallest of changes that any file change or dependency update may create.

However this depth is also its downside. In larger projects with many contributors, who may not understand the exact details of each component, developers are too tempted to automatically update them without inspecting the full change, as often this is a minor implementation detail that has changed. The value of the test is then drastically reduced, to the point many feel it provides false confidence and should not be used at all.

An expectation would look like:

expect(container).toMatchSnapshot();

Snapshots will be saved for you beside the test file that created them in an auto-generated __snapshots__ folder.

The decision to use snapshots or not is yours to make, and is specific to your project and your team.

Accessibility

Although we learned above that only ~30% of a11y issues can be found by automated testing, that is 30% more than no testing at all. Adding the below can assist with this.

import React from 'react';
import { axe } from 'jest-axe';
import { render } from '@testing-library/react';
import MyComponent from './MyComponent';it('should not have basic accessibility issues', async () => {
const { container } = render(<Component />);
const results = await axe(container); expect(results).toHaveNoViolations();
});

Conclusion

jest, jest-dom, react-testing-library, and jest-axe work smoothly together to form a versatile testing toolkit for testing React components from a functional user perspective.

Whether this testing philosophy matches your own is only for you to decide, but hopefully this provides an entry point to seeing what is possible. The documentation of each is good and is linked in the resources section below.

Thanks for reading! 😊

If you liked this, you might also like:

Resources

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

--

--