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
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:
screen
: Thescreen
API allows you to access the HTML DOM, and print out either the entire DOM or a portion of it using thedebug
method. You can also retrieve element(s) by using thefindBy*
,queryBy*
andgetBy*
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*
andgetAllBy*
are variants of the respective methods that let users retrieve an array of matching elements instead of a single element.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.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.fireEvent
:fireEvent
allows you to handle trigger events on the different elements likeinput
orselect
. An alternative is theuserEvent
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.
Top comments (0)