DEV Community

Cover image for Testing with Jest and React Testing Library
Aadit Kamat
Aadit Kamat

Posted on

Testing with Jest and React Testing Library

Jest is a library developed by Meta for testing JavaScript applications. React Testing Library, on the other hand, is a library developed by Kent C Dodds and other open source contributors, which wraps around the React library and provides a black box solution specifically for unit testing React applications.

At Visa, I was writing unit tests for a Next.js project using components designed with Chakra UI. That's where React Testing Library came in handy. Unlike other solutions like Enzyme, I did not have to worry about the application snapshot but could instead focus on each UI element, its expected behaviour and the data it would render upon user interactions.

Jest

In Jest, you usually split your tests into groups using the describe block. You can write a descriptive message as the title of the block to indicate what kind of tests you are including in this block. Similarly, each individual test case is written using the it or test wrapper, with a message to describe the individual test case.

The nice thing about Jest - which can also be a pain point when you actually get down to writing the tests - is that they execute in parallel rather than sequentially. You can force sequential execution by using the --runInBand parameter and this comes in handy sometimes while debugging issues related to timing. However, test suites are generally executed in parallel so that they run faster.

jest example.test.js --runInBand
Enter fullscreen mode Exit fullscreen mode

With Jest, you write tests using the expect clauses, where you specify the matcher that you are expecting and compare it to the received output, to verify that the expected behaviour is satisfied.

You can specify the chunks of code you wish to execute before and after each test case using the beforeEach and afterEach clauses respectively. If you want to include some code before all the test cases are executed or after all the test cases are executed, you can use the beforeAll and afterAll clauses. These functions are very useful for consolidating all the common data setup as well as doing some cleanup.

Mocking is a useful feature in Jest that allows you to test the expected behaviour when an API call is made without actually calling the respective APIs. This is necessary for testing because calling the actual APIs can be costly, since these calls are subject to constraints like network bandwidth. These can also be used for simulating calls to third party dependencies, which are not supposed to be tested because they have been developed by others.

React Testing Library

These are the abstractions (functions) from the React Testing Library that I have used most for writing unit tests:

  1. screen: The screen API allows you to access the HTML DOM, and print out either the entire DOM or a portion of it using the debug method. You can also retrieve element(s) by using the findBy*, queryBy* and getBy* functions. findBy* is an asynchronous function that allows you to fetch an HTML Element or an array of HTML Elements. queryBy*, on the other hand, allows you to fetch either as well as return null if they are not found. getBy* throws an error if the element(s) in question is not found. findAllBy*, queryAllBy* and getAllBy* are variants of the respective methods that let users retrieve an array of matching elements instead of a single element.

  2. waitFor: waitFor allows you to execute a block of code and wait for it to finish before you move on to the other blocks of code in the test case.

  3. render: render is used to render a React Component or Fragment as you would in a React application. This is basically a re-export of the original render function from the React library.

  4. fireEvent: fireEvent allows you to handle trigger events on the different elements like input or select. An alternative is the userEvent library for simulating the user interactions.

Example of a unit test case

Let's say we build a login form using Chakra UI. We want to do very basic validations on the form data and check the logic flow once the submit button is called.

You only need to specify the respective test ids for the components while designing the front end. You can then match the values filled in with the expected values after carrying out the interactions. Here, the mock for the console object comes in handy to determine whether the statement is printed upon successful login or not.

Pain points:

While writing unit tests seems straightforward in theory, it requires a good amount of trial and error in practice.

These are some of the issues that I faced along the way:

1. Time out issues

If the tests involve a large set of UI interactions like filling in multiple fields in a form or handling multiple input events, they are likely to time out before the whole test case executes, or even in the waitFor block. A work around is using fakeTimers in jest and then runAllTimers once the component is rendered so that the test timer waits for the completion of the execution. Another way, is to use setTimeout and specify the timeout you are expecting.

The main problem is detecting the line(s) of test code responsible for the delay in test execution. For this, you have to usually resort to some trial and error with reordering the test cases in the suite or even the way you break the test cases into suites. You might also have to log the execution of the source code at several points to see whether the data obtained from a mock is the same one that the test was expecting at that point in time. The problem is compounded due to the asynchronous nature of execution of React code in hooks.

2. Test cases pass individually but fail when run in a suite

Honestly, this is a very frustrating problem to solve. The first easy step to troubleshoot this issue is to have all the shared data setup for the test cases in the beforeEach and afterEach clauses. However, it could also be that some of the tests have taken longer to execute than others and therefore, only part of the source code gets tested in a failing case. Resolving this issue involves significant trial and error similar to the timeout issue.

3. Element not found

Sometimes the element(s) you are looking for simply cannot be found. You would have to print out the whole DOM using debug and inspect why this is not found. It's an easy fix if the matcher specified in the expect clause is wrong, but it becomes quite troublesome if the element is not displayed because the state of the application has not been updated. This might require you to inspect the application flow more thoroughly. It's good if the test has helped you catch the bugs in code execution, since that is the main purpose behind software testing. However, it is also possible that your code is executing correctly but there is some other issue that results in this error, like a timeout issue.

Oldest comments (0)