Testing accessibility of React components with Jest, Testing Library and Axe

Using automated testing tools we can cover some of the most common accessibility issues that might arise in our web interfaces. Just be aware that automation won't catch everything. Axe, the tool we'll be using in this article, catches about 57% of issues, which is not bad, and certainly better than nothing, but a manual audit is still necessary for complete coverage. We'll use three tools in tandem to achieve a nice testing experience, while testing both the regular aspects of our components, and the accessibility.

Jest

Jest is our test runner. If you've used React before, especially via Create React App, then you'll probably be familiar with it. When Jest runs it picks up all test files it can find in your project and runs the tests described in them. When using it to test React components, the code in your test files can look something like this:

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";

import Button from "./button";

let container = null;
beforeEach(() => {
container = document.createElement("div");
document.body.appendChild(container);
});

afterEach(() => {
unmountComponentAtNode(container);
container.remove();
container = null;
});

it("renders with content", () => {
act(() => {
render(<Button id="B1">I am button</Button>, container);
});
const button = container.getElementById("B1");
expect(button.textContent).toBe("I am button");
});

Testing Library (and jest-dom)

As you can see in the example above it does require a bit of boilerplate code to get things working right. You'd be forgiven if you are tempted to write some nice helper functions to streamline things and make the code look a bit cleaner and more readable.

Testing Library, however, is going to accomplish just that, and more. It is a library that provides a bunch of functions that not only looks nice and readable, but also encourages you to write your tests in a user-centric manner. The guiding priciple of Testing Library is one I wholeheartedly agree with:

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

Install the React version of Testing Library by running:

npm install --save-dev @testing-library/react

No further configuration is required, we can now import and use functions from Testing Library in our test files to render components and also query for things in the result of the render.

Also install jest-dom:

npm install --save-dev @testing-library/jest-dom

Jest-dom is a companion library that allows us to make nice readable assertions on the things we have queried with Testing Library. It needs a tiny bit of configuration to work: in your jest setup file, import the library like so:

import "@testing-library/jest-dom";

And that's it. Now we can write some quite eloquent testing code - the example above now looks something like this:

import React from 'react';
import { render } from '@testing-library/react'

import Button from "./button";

it("renders with content", () => {
const { getByRole } = render(<Button id="B1">I am button</Button>);

expect(getByRole('button')).toHaveTextContent('I am button');
}

Looks pretty readable if you ask me.

Axe

Axe is a library that tests the accessibility of web documents. We can use it when running tests with Jest by using jest-axe. Install it like so:

npm install --save-dev jest-axe

This library requires a similarly tiny bit of config. Just like with jest-dom, put the following in your jest setup file:

import "jest-axe/extend-expect";

Now we are ready to write an automatic accessibility test. We'll put it after our previous test, and the final test file looks like this:

import React from 'react';
import { render } from '@testing-library/react'
import { axe } from 'jest-axe';

import Button from "./button";

it("renders with content", () => {
const {getByRole} = render(<Button id="B1">I am button</Button>);

expect(getByRole('button')).toHaveTextContent('I am button');
}

it("Passes automatic accessibility tests", async () => {
const { container } = render(<Button id="B1">I am button</Button>);
expect(await axe(container)).toHaveNoViolations();
})

Conclusion

Using these tools you can form a good foundation to build on when you start writing tests for React components. The user-centric approach enabled and encouraged by Testing Library is something that brings a lot of value to your tests, and the automatic accessibility tests sets a standard for the quality of your code - maybe even your level of commitment to building good content for the web!