Test After Development (TAD) with React Testing Library & Jest

Tejashree Bandi
make it heady
Published in
8 min readMay 19, 2020

There are multiple tools and libraries in React for testing. These include Jest, React Testing Library, Mocha, Chai, Karma, Jasmine, Cypress IO, Puppeteer, Enzyme, and more. Is there a good reason to choose one of these over the others? Personally, I prefer React Testing Library (RTL) to test my React components.

Many developers choose to work with Enzyme or React Testing Library from list of testing libraries mentioned above. Initially, I had started with Enzyme and Jest. Jest is mainly used for assertion, while Enzyme deals with testing react component like render components, and checking things like props and states. But as our team started to work with React hooks for our applications, Enzyme was no longer able to meet our requirements. (Despite these issues, I wouldn’t say that it’s bad practice to use Enzyme, but it does take extra effort to write the right sort of code practices.)

After looking for an alternative, I started using React Testing Library (RTL). With RTL, I found that I could write cleaner code and user-interactive test cases. When I converted my Enzyme code to RTL, I was able to write more optimized code, and could remove many lines of code. RTL interacts with DOM nodes; it doesn’t care whether the state is true or false. Since RTL works more directly with DOM nodes, using it with Jest-DOM can improve assertions.

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

Test Driven Development (TDD) is the process of testing specific parts of an application’s behavior. A healthy approach to TDD is to first define the required behavior tests, and then keep on passing the tests through development.

Installation

If we start our application with create-react-app, we don’t need to add any package installation.

Otherwise, we will have to install the following dependencies:

# add with npm 
npm install --save-dev @testing-library/react
# or with yarn
yarn add --dev @testing-library/react
# add with npm
npm install --save-dev @testing-library/jest-dom
# or with yarn
yarn add --dev @testing-library/jest-dom

Jest: We are using Jest for assertion in our tests. Jest is another fast-testing framework, with features like mocking, snapshot testing, and great assertions. It is developed and used by Facebook for its react applications. With create-react-app, there’s no installation headache to set up the Jest.

File and Folder Structure

Standard guidelines would call for the creation of a __ test __ folder, to hold onto test files. However, I approached this in a different way.

Personally, I don’t see the advantages of adding a__ test __ folder. This is in part because when Jest crawls your root directory, it only searches for test files to run; it doesn’t look for a folder. Hence, my personal choice is to place test files in the component folder itself.

- node_modules
- public
- src
- components
- TestComponent
- TestComponent.jsx
- styles.scss
- TestComponent.test.js // here is what I do different
- index.js // source of truth for component export
- utils
- helpers.js
- App.jsx
- App.test.jsx
- App.styles.js
- index.js

Before Starting with Tests

Some basic things to know before moving ahead:

it or test: This basically describes the test. It requires two parameters, like the name of the test and a function that holds the entire test.

expect: Condition needed to pass the test. Expect condition will compare the received parameter to a matcher.

matcher: A function that corresponds to the situation you expect.

render: A function to render a given component which returns a React element.

Snapshot Testing

As the name suggests, snapshot testing generates a mirror image of the DOM node of our React component.

To check a snapshot, first, we need to get our jsx working. Then we can generate a snapshot of it, using theyarn test/npm test command. This will allow us to test if the passed data is properly reflected in the component. Jest will compare the snapshot to the generated output for the test.

If the test fails, that means some unexpected issues occurred. We’ll need to make some changes in the component to fix the issue and get the expected results, by generating a new snapshot and making sure we pass the test.

Now, let’s try to generate a new snapshot of the component.

We will create a snapshot for our very basic App.js file by creating an App.test.js file:

//App.test.jsimport React from 'react'
import {render, cleanup} from '@testing-library/react'
import App from './App'

afterEach(cleanup)

test('should generate a snapshot for app component', () => {
const { asFragment } = render(<App />)

expect(asFragment(<App />)).toMatchSnapshot()
})
});

To take a snapshot, first, we have to import render and cleanup from RTL. These two methods will be used in almost all of our test cases.

render is a function to render a given component, which returns a React element. And cleanup is passed as a parameter to afterEach to clean up everything after each test and avoid memory leaks.

Now, we’ll run the test command:

# add with npm 
npm test
# or with yarn
yarn test

This will create a new folder __snapshots__ and a file App.test.js.snap in the src which will look like this:

//App.test.js inside __snapshots__ folder
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Take a snapshot should take a snapshot 1`] = `
<DocumentFragment>
<div class="App">
<h1>Testing</h1>
</div>
</DocumentFragment>
`;

If we make any change in App.js, our test will fail, because the snapshot will no longer match the component. To pass the test, we’ll need to press u to update the snapshot. Now, we should be able to pass the test and get the updated snapshot in App.test.js.snap.

Testing a List of Items

To test a list of React DOM elements, we will create a file called List.js inside src -> components ->List and paste the below code:

Then we’ll create a file named List.test.js inside List folder and paste the below code:

In the first test (“should render List component”): I have rendered the List component and checked its existence. In the current scenario, we should pass the test.

In the second (“renders data correctly”): I am trying to render correct data. I have created one fake JSON object to compare with my actual data, and expect that it should get the same results from my actual data.

In the last one (“Total length of list should be 3”): I am trying to get the total length of my DOM Node child elements (number of li’s).

Now if we save the test file and run again in our terminal yarn test/npm test, the test should pass.

Woo-hoo! We’re are done with our first test, and we passed!

Photo by Austin Schmid on Unsplash

Test Component Events

To test React DOM elements, we’ll create a file called Button.js inside src -> components ->Button and paste the below code:

Next, we’ll create test file called Button.test.js in the same Button folder and paste the below code:

The component receives a callback function, and renders a button. So we have to test whether the callback function is called when the button is clicked.

Like the List example above, I rendered the component and used an assertion to check its existence. After that, I rendered the component again with different props (for example, text=‘Go’ as a prop), and expected the changes in assertion.

In the second test (“calls correct function on click”): to test the click event I used Jest’s mock function. Mock function lets us inspect the behavior of a function.

fireEvent.click creates a click event and sends that event on the given DOM node. This is a good way to test what happens when any action or event is fired.

In the last test (‘The button should be enabled’): I am using toHaveAttribute method. This checks that the disable attribute is not present in the button element. Now we can run the test, and it should work as expected.

Test Asynchronous Actions

As the name indicates, we will be using async/await to handle the events and two mock functions to request and resolve the data. To do so, we’ll create Employees.js in component -> listEmployees folder and paste the below code:

Next, we’ll create another test file named with Employees.test.js, using the below code:

For an example of this Employee component: if we click on Get Employee List button, it calls a fetchEmployees function. To get the response, it calls the listEmployees callback function, which returns a promise to display a list of employees. So, we can say that the listEmployees callback function is in some parent component which performs some async request response API, and returns a promise in array.

describe():- As you can see, I have used describe(), which is used to divide test cases in some groups within the same file. I have only one test here, but I just wanted to illustrate the concept of describe(). If we had additional code in the Employee component or we added more test cases in this file (like error, DOM nodes, or other fire events), we could use describe() more effectively.

Now, let’s move ahead with the test. To test an async action, we have to pass a mock function that will return a promise. I have created a mock function using jest.fn(), which is used to resolve the promise.

First, we render the component by passing the listEmployees callback function as a prop. In the first assertion, it gives us empty results, because the click event has not fired yet. So, we should get a pass result.

Next, we fire a button click event, it calls listEmployees callback function and returns a promise to set our state employees .

During this process, if we check the emp-wrap element, it will still shows as empty, because the code is waiting for the listEmployees promise to return. Until the promise returns, we can use the waitForElement function to wait, until the element exists in the DOM. Once the DOM is ready with all the listing elements, we can now check with our assertion whether the content of the result matches the passed mock data.

Now, if we test this using yarn test/npm test, we should pass.

Using the above steps, we can check for an api error state by passing another mock function.

Abstract

React Testing is a great package for unit testing. It has many options for matching nodes, useful methods for firing DOM events, various utilities to deal with async code, and many more. I am still exploring it, but I personally enjoy working with RTL.

If this article helped you, I suggest that you to start with this package in your future developments. Thank you.

P.S : We're hiring at Heady! 👋 If you would like to come work with us on some really cool apps please check out our careers page.

--

--