Testing Library is a JavaScript testing framework that focuses on testing the way the application is used. Testing Library will also help you avoid testing implementation details and make your tests more maintainable. Testing the way the application is used will give us the confidence that the application is working as intended.
What's also nice about testing-library, is that its recommended queries are designed to work well on accessible elements. This means using Testing Library will also reveal accessibility problems in your application.
In this article, we'll go through a few examples where Testing Library will reveal accessibility problems in your application.
While this article uses React components as examples, Testing Library supports many other frameworks and the DOM API works with any environment that provides DOM APIs, such as Jest, Mocha + JSDOM, or a real browser
Button
The first example we'll look at is implementing a button. Here's a naive way of creating a button component.
function Button(props) {
return (<div className="btn" onClick={props.onClick}>{props.children}</div>);
}
test("button exists", () => {
render(<Button>Click Me</Button>);
expect(screen.getByRole("button", {name: /click me/i})).toBeInDocument();
})
Here we have a button implemented using a div
element and when you try to get it using a getByRole
query in your tests you'll quickly realize it doesn't work.
The test will fail because the query can't find the button. Now, you might just use getByText
and call it a day. But the problem is, screen readers won't recognize the div-button as a clickable element and the users who depend on them won't be able to use your application at all!
The best way to fix this is to just use the button
element instead of a div
element. This will ensure it will be visible to assistive technologies.
If for some reason you still need to use div
you can add the role
attribute to the element.
<div className="btn" role="button" onClick={props.onClick}>{props.children}</div>
Implementing buttons using divs might seems like a far fetched example but well, it happens 😅
Modal
As the next example, we will look at implementing a modal. Here's a simple modal implementation.
function Modal({open, title, children}) {
return (
<div className="backdrop" style={{display: open ? "block" : "none"}}>
<div className="modal">
<h3>{title}</h3>
<div>
{children}
<div>
</div>
</div>
)
}
test("modal has correct title", () => {
render(<Modal title="Modal title">modal content</Modal>);
expect(screen.getByRole("dialog", {name: /modal content/i})).toBeInDocument();
})
This test will fail to find the dialog, and from the perspective of assistive technologies, the modal might as well not exist. You could get around this issue by querying the div element with the class modal
but then you would be testing implementation details. What happens when someone decides to change the class name?
Instead, you should make the modal accessible by adding role
, aria-modal
, and aria-labelledby
attributes.
role
identifies the element as a dialog
aria-modal
indicates that the elements under the dialog can't be interacted while the dialog is open
aria-labelledby
gives the dialog an accessible name by referencing the element which gives the dialog its title
<div className="modal"
role="dialog"
aria-modal="true"
aria-labelledby="dialog-title">
<h3 id="dialog-title">{title}</h3>
<div>
{children}
<div>
</div>
Reach UI
For React developers I recommend using Reach UI instead of implementing controls, modals, etc. completely from scratch. It is an accessible foundation for your own components and makes creating accessible design systems easy.
Accessibility is hard to get right and while using Testing Library will help you discover some obvious issues, it's not a replacement for proper accessibility testing. (Edited 25 Feb 8:30 PM EEST)
Other resources
Testing Library
Commong mistakes testing by Kent C. Dodds
Common mistakes with React Testing Library by Kent C. Dodds
Photo by Daniel Ali on Unsplash
Top comments (2)
Be aware that manual testing is still needed for a11y. For example, in your code button has
role
, but doesn't havetabIndex
, which means that users which don't use mouse can't focus the button and trigger an actionYou're absolutely right 👍 This is no replacement for proper a11y testing but will uncover some issues.