2021-01-17
|~5 min read
|815 words
Jest has a robust assertion library, but it’s also a general purpose tool. This can lead to some awkward tests, or imperative at least.
Fortunately, Jest’s assertion library is extensible! For example, testing-library
has a whole suite of assertions specifically for the DOM designed to tie in with Jest. It’s called jest-dom
and includes a number of Custom matchers. These matchers are designed to replace traditional assertions like toBe
with more idiomatic variants like toHaveTextContent
.
So, how is it used?
This post will walk through:
testing-library
’s jest-dom
jest.config.js
to avoid boilerplate.Which dependencies need to be installed will vary based on which assertion library you want to install in order to extend Jest. However, in the case of this demo, we’ll be using @testing-library/jest-dom
, so, we’ll add that as a dev dependency in our package.json
:
% yarn add --dev @testing-library/jest-dom
Now that we have the dependency installed, we can modify an existing test. Let’s take this test checking the contents of a button and modify it.
The standard Jest test is fairly imperative, relying on the engineer writing the test to specify exactly what should be equal to what (i.e., the button’s textContent
should be “Click Me”):
import React from "react"
import { render } from "@testing-library/react"
import Component from "../component"
test('the component renders with "Click Me" as its text', () => {
const { getByTestId } = render(<Component />)
const button = getByTestId("test-button-id")
expect(button.textContent).toBe("Click Me")
})
If we import jest-dom
, we can write a more declarative test:
import * as jestDOM from "@testing-library/jest-dom"import React from "react"
import { render } from "@testing-library/react"
import Component from "../component"
expect.extend(jestDOM)
test('the component renders with "Click Me" as its text', () => {
const { getByTestId } = render(<Component />)
const button = getByTestId("test-button-id")
expect(button).toHaveTextContent("Click Me")})
By importing jest-dom
we now have new assertions available once expect has been extended. In this case, we’re using the toHaveTextContent
and targeting the entire element, instead of specifying the textContent
of the element.
While writing more idiomatic code is a benefit, there’s likely an ever larger benefit to using jest-dom
: clearer error messages in the event that the test fails.
For example, using the original test would print the following:
FAIL client src/component.test.js
● the component renders with "Click Me" as its text
expect(element).toHaveTextContent()
//highlight-start
Expected: "Click Me"
Received: "Click Me!"
//highlight-end
15 |
> 16 | expect(button.textContent).toBe('Click Me')
| ^
17 | })
18 |
It’s easy to see that the expect
doesn’t match the received
. What’s less clear, however, is what those terms represent. To see that we need to look at the code block and see that we’re comparing the textContent
of a DOM element to text.
Contrast that with the error printed when using the jest-dom
assertions:
FAIL client src/component.test.js
● the component renders with "Click Me" as its text
expect(element).toHaveTextContent()
//highlight-start
Expected element to have text content:
Click Me
Received:
Click Me!
//highlight-end
15 |
> 16 | expect(button).toHaveTextContent('Click Me')
| ^
17 | })
18 |
Now it’s clear that we’re talking about an element and, specifically, inspecting it’s text content even before we get to the code block.
Now that we’re getting real gains by extending Jest’s assertion library, let’s tackle the boiler plate code so that we can get these benefits without it!
In the most basic scenario, demonstrated above, every time we want to use the more specific assertions available via jest-dom
, we have two additional lines in every file:
jest-dom
expect
Fortunately, in the case of jest-DOM
, ‘@testing-library’ exposes /extend-expect
to slim this down to one line:
import "@testing-library/jest-dom/extend-expect"
But, still, we would need to do that for every test.
This is where Jest really shines in its flexible configuration. Jest has a setupFilesAfterEnv which accepts a list of modules and will run them before each test suite is executed.
These files could be relative paths (e.g., a ./jest-setup.js
file), or modules within node_modules
, which is the route we’ll take here:
module.exports = {
setupFilesAfterEnv: ["@testing-library/jest-dom/extend-expect"],
}
With this in place, we do not need to have the import in our files but we still have access to the extended expect
methods.
Here we’ve walked through how to extend Jest’s native assertion libraries by demonstrating how to use testing-library
’s jest-dom
package. In doing so, we were able to make our tests more declarative, improve the readability of our error messages, and avoid additional cruft in our test suites through the use of Jest’s setupFilesAfterEnv
API in its configuration.
Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!