Mastering React Unit Testing with Jest: A Comprehensive Guide

Testing is a crucial aspect of software development that ensures the quality of the product and helps to identify bugs and issues in the code. Automated testing is an effective way to test software quickly and efficiently, and it has become increasingly popular in recent years.

In this blog, we will explore the basics of automated testing and discuss its importance in the software development process. We will also take a closer look at unit testing in React.js and the benefits of using Jest, a popular testing library for JavaScript applications. With Jest, we can write unit tests for React components and ensure that our code is working as expected. So, let’s dive in and learn more about unit testing in React.js with Jest.

Prerequisites

Before we dive into the details of unit testing in React.js with Jest, it’s important to ensure that we have the necessary prerequisites in place. To get started with Jest, we need to have an understanding of JavaScript, NPM, and React.js. Also, Jest uses Babel to transpile your code, so it is important to have some knowledge of Babel. We also need to have Node.js and NPM (Node Package Manager) installed on our system.

What is Testing, and What are its Different Approaches?

Testing is an important element of the software development process because it ensures that the product works as intended and meets the requirements and standards that were established. Product analysis and testing help in the early detection of defects, errors, and other issues, allowing them to be rectified before the deployment.

Manual testing and automated testing are the two basic methods of testing. Human testers run various test cases to validate the product’s functionality during manual testing. Manual testing can be time-consuming and repetitious.

On the other hand, automated testing uses computer programs to run tests and verify a product’s functionality. Automated testing is more efficient and faster than manual testing, and it can help detect flaws early in the development cycle. Automated testing allows us to test the code frequently and in less time. It helps us to catch the bugs before the deployment and lets us deploy the application with more confidence.

Automated testing has grown in popularity in recent years. The following section will concentrate on automated testing, specifically unit testing with Jest in React.js.

Automated Testing and its Different Types

Automated testing is the practice of writing code to test our code and then running those tests in an automated fashion.

Automated testing is frequently used to improve efficiency and accuracy in the testing process. Automated testing can also help to reduce the risk of human error, as it follows predefined steps and criteria, which helps to ensure that each test is performed consistently and objectively.

There are several different types of automated testing, including Unit testing, Integration testing, and End-to-End testing.

🔹 Unit Testing

Unit testing is a type of testing that focuses on testing individual units or components of the software application without their external dependencies.

It involves testing each unit in isolation to ensure that it is working correctly.

Related Read: Angular Unit Testing: Jasmine & Karma

Unit Testing

 

🔹 Integration Testing

Integration testing is the process of testing different units or components of the software application with their external dependencies.

It involves testing the interactions between different units to ensure that they are working correctly together.

Integration-Testing

Integration-Testing

🔹 End-to-end testing

End-to-end testing is a type of testing that tests the entire software application from start to finish.

It involves testing the software as a complete system to ensure that it is working correctly. In end-to-end tests, we only cover the happy paths and leave the edge cases to unit tests.

The actual ratio between unit, integration, and end-to-end tests depends on your project.

Test Pyramid

The test pyramid is a conceptual framework that is used to guide the creation of automated testing strategies.

It states that we should test the little sections first, then how they interact with one another, and finally the software as a whole.

This allows us to ensure that the software runs well and detect problems early. It is critical to employ a variety of tests to ensure that nothing is overlooked. In this manner, we can be certain that the application works well and has no hidden flaws.

Test Pyramid

Here are some of my personal takeaways from this test pyramid:

  1. Favor unit tests to End-to-end tests. Because unit tests are faster to run, cheapest to write, and give you rapid feedback.
  2. Cover unit test gaps with integration tests.
  3. Use End-to-end tests sparingly. Only for the key function of your app.

What is Jest and Implementation of Unit Testing with Jest in React.js

Jest is a well-known JavaScript testing framework created by Facebook. It is intended to be quick and simple to use, while still providing a full testing experience for JavaScript projects such as Node.js apps, React applications, and others.

Jest features utilities for constructing test suites, defining test cases, and asserting anticipated outcomes in a straightforward and clear API for writing tests. It also contains built-in support for mocking, code coverage reporting, and parallel test execution.

Overall, Jest is a robust and frequently used testing tool that may assist developers in ensuring the quality and dependability of their JavaScript code.

Related Read: Jest Tutorial: Writing Test Cases Using Jest

Let’s see the list of available libraries for testing React.js applications.
Below is the list of some popular libraries for testing React.js applications:

  1. Jesmin
  2. Mocha

Whenever it comes to testing the React.js application the first question that comes to our mind is what to test and what not to test. Let’s see some standard points about it.

🔹 What to Test?

  • We should test component renders
  • We should test component renders with props
  • We should test component renders in different states
  • We should test components’ React to events

🔹 What Not to Test?

  • We should not test the implementation details
  • We should not test the third-party library code
  • We should not test code that is not important from the user’s point of view

Implementation Steps of Jest in React.js

To get started with Jest, you need to install all required libraries for implementing Jest using npm. It is important to note that these libraries should be installed as dev dependencies since they are only needed in the development environment, not in production.

The command for installing all the required libraries can be found below. After installation, a folder structure can be created for organizing tests and configuring the Jest environment.

npm i --save-dev jest @testing-library/jest-dom
@testing-library/react @testing-library/user-event
@testing-library/dom

🔹 Folder Structure

The suggested folder structure for tests in a React project using Jest is often located in a __tests__ directory at the root of the project or in the same directory as the code being tested.

Tests should be grouped into subdirectories within the __tests__ directory to reflect the structure of the code being tested. For example, if your React project has a components directory with many components, you may establish a components directory under __tests__ and write tests for each component in its own file within that directory.

Here is the folder structure that I personally follow:

my-app/
  src/
    components/

       __tests__/
          components/
             Header.test.js
             Button.test.js
             ...
          utils/
             formatNumber.test.js
             ...
      Header.js
      Button.js
      ...
    utils/
      formatNumber.js
      ...
  ...

There is a __tests__ directory in this example, with subdirectories for components and utils. Each test file ends in.test.js tested by the Jest.

This is the one possible folder structure for arranging tests and the particular structure may differ based on your project’s needs.

Every test we write generally involves the following basic steps:

  1. Render the component
  2. Find the element rendered by the component
  3. Assert again the element that we found in step 2, which will pass or fail the test.

Let’s take one example of a counter component and see how we can write test cases for it:

Here is our counter component:

import React, { useState } from "react";

const Counter = ({ initialValue }) => {
 const [count, setCount] = useState(initialValue);
 const handleIncreament = () => {
   setCount((count) => count + 1);
 };
 const handleDereament = () => {
   setCount((count) => count - 1);
 };
 const handleReset = () => {
   setCount(0);
 };
 const handleSwitchSign = () => {
   setCount((count) => count * -1);
 };
 return (
   <div style={{ textAlign: "center" }}>
     <h1>{count}</h1>
     <div>
       <button onClick={handleIncreament}>Increament</button>
       <button onClick={handleDereament}>Decreament</button>
       <button onClick={handleReset}>Reset</button>
       <button onClick={handleSwitchSign}>Switch Sign</button>
     </div>
   </div>
 );
};

export default Counter;

Here is test file for the upper component:

import React from "react";
import Counter from "../src/components/Counter";
import { render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom";

describe("Counter component", () => {
 test("renders initial count", () => {
   const { getByText } = render(<Counter initialValue={0} />);
   const countElement = getByText("0");
   expect(countElement).toBeInTheDocument();
 });

 test("increments count when increment button is clicked", () => {
   const { getByText } = render(<Counter initialValue={0} />);
   const incrementButton = getByText("Increament");
   const countElement = getByText("0");
   userEvent.click(incrementButton);
   expect(countElement).toHaveTextContent("1");
 });

 test("decrements count when decrement button is clicked", () => {
   const { getByText } = render(<Counter initialValue={0} />);
   const decrementButton = getByText("Decreament");
   const countElement = getByText("0");
   userEvent.click(decrementButton);
   expect(countElement).toHaveTextContent("-1");
 });

 test("resets count when reset button is clicked", () => {
   const { getByText } = render(<Counter initialValue={5} />);
   const resetButton = getByText("Reset");
   const countElement = getByText("5");
   userEvent.click(resetButton);
   expect(countElement).toHaveTextContent("0");
 });

 test("switches sign of count when switch sign button is clicked", () => {
   const { getByText } = render(<Counter initialValue={3} />);
   const switchSignButton = getByText("Switch Sign");
   const countElement = getByText("3");
   userEvent.click(switchSignButton);
   expect(countElement).toHaveTextContent("-3");
   userEvent.click(switchSignButton);
   expect(countElement).toHaveTextContent("3");
 });
});

The code defines a Counter component, which takes an initial number as a prop and shows a count on the page. The component has numerous buttons that can be used to increment or decrement the count, reset it to zero, or change the sign of the count.

Here we have used describe keyword which is basically used to group the test cases in Jest. We have imported render from @testing-library/react library for rendering the component. We have also imported for userEvent for performing the user interaction in our case we have used it for clicking the button. And we have imported @testing-library/jest-dom for performing the assertion. Here the expect keyword is used for assertion. And the function which comes after assertion in the matcher function. There are several types of mather functions that we case use according to our needs. You can find more about the matcher function here.

Each test case ensures that the Counter component responds correctly to certain user input, such as hitting the increment button. The test cases employ Jest’s expect assertions to ensure that the count updates appropriately.

For finding elements on the page we use the RTL queries. Let’s see about RTL Queries in detail.

RTL Queries:

Queries are the methods that the testing library provides to find elements on the page.
To find a single element on the page we have the below queries:

  • getBy…
  • queryBy…
  • findBy…

To find multiple elements on the page we have the below queries:

  • getAllBy…
  • queryAllBy…
  • findAllBy…

The suffix can be one of the Role, LabelText, PlaceHolder, Text, DisplayValue, AltText, Title and TestId.

🔹 queryBy… Query

queryBy… query returns the matching node. And throws a descriptive error if no elements match or if more than one match is found. Below is an example of getBy… queries:

🟢 getByRole: This query returns elements based on their role attribute, which describes the element’s purpose. A button element, for example, might have the role “button” to indicate that it is a clickable button. Here’s an illustration:

// HTML
<button role="button">Click Me</button>

//Jest Test
const button = getByRole('button');
expect(button).toHaveTextContent('Click Me');

🟢 getByLabelText: This query is used to retrieve form components based on their label text. For example, an input element may be connected with a label element that specifies the purpose of the input. Here’s an illustration:

// HTML
<label htmlFor="name">Name:</label>
<input id="name" type="text" />

// Jest Test
const nameInput = getByLabelText('Name:');
expect(nameInput).toBeInTheDocument();

🟢 getByPlaceholderText: This query retrieves input components based on their placeholder text, which serves as a hint to the user about the expected input. Here’s an illustration:

// HTML
<input type="text" placeholder="Enter your name" />

// Jest Test
const nameInput = getByPlaceholderText('Enter your name');
expect(nameInput).toBeInTheDocument();

🟢 getByAltText: This query is used to get image elements by their alt text, which is used to describe the image to users who cannot see it. Here is an example:

// HTML
<img src="cat.jpg" alt="A cute cat" />

// Jest Test
const catImage = getByAltText('A cute cat');
expect(catImage).toBeInTheDocument();

🟢 getByTitle: This query returns elements based on their title attribute, which is used to display more information about the element when the user hovers over it. Here’s an illustration:

// HTML
<a href="/" title="Go Home">Home</a>

// Jest Test
const homeLink = getByTitle('Go Home');
expect(homeLink).toHaveAttribute('href', '/');

🟢 getByTextId: You can use getByTestId to get elements by a data-testid attribute. Here are examples:

// HTML
<div data-testid="submit-button">Submit</div>

// Jest Test
const submitButton = getByTestId('submit-button');
expect(submitButton).toHaveTextContent('Submit');

The priority of using upper RTL queries depends on the specific use case and the type of elements being tested. However, getByTextId and getByRole are commonly used to test for specific elements and their accessibility roles respectively. getByLabelText is often used to target form elements and getByAltText is used to ensure images have appropriate alt text. Finally, getByPlaceholder can be used to test form placeholders. And at last, if no query works for you, you can use getByTestId.

🟢 queryBy… Query: It returns the matching node for a query, and returns null if no element matches.
It is useful for asserting an element that is not present. It throws an error if more than one match is found. Example:

test("queryByRole example", () => {
const { queryByRole } = render(<MyComponent />);
const button = queryByRole("button");

// Assert
expect(button).toBeInTheDocument();
});

🟢 findlBy… Query: The findBy queries return the first matching node for a specific selector, just like the getBy queries. However, they are asynchronous and wait for the element to appear in the DOM if it is not present at the time of invocation. Example:

test('findByRole example', async () => {

const { getByRole, findByRole, queryByRole } = render(<MyComponent />);
const button = getByRole('button');
expect(button).toBeInTheDocument();
fireEvent.click(button);

// Assert
const modal = await findByRole('dialog');
expect(modal).toBeInTheDocument();
});

For a more comprehensive understanding of the RTL Queries, I suggest referring to the RTL Queries Docs. These docs provide detailed information on how to use RTL Queries to test your React components effectively.

User Interaction

The @testing-library/user-event library is a utility for simulating user events in your tests. It can be used with Jest to simulate user interactions with a component such as clicks, inputs, keyboard events, and more.

For example, to simulate a user clicking a button in your React component, you can use userEvent.click() method provided by this library. Here’s an example:

import React from "react";
import { render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

import Button from "./Button";

test("Button changes text when clicked", () => {
const { getByText } = render(<Button />);

const buttonElement = getByText("Click me");
userEvent.click(buttonElement);

expect(buttonElement).toHaveTextContent("Clicked");
});

In this example, we are rendering a Button component and using the getByText method from the @testing-library/react library to get the button element. Then we are using userEvent.click() method to simulate a click event on the button. Finally, we are using the toHaveTextContent matcher to assert that the button text has changed to “Clicked” after the click event.

You can learn more about the user-event library here.

Throughout this blog, I have provided an overview of unit testing in React using Jest, focusing on essential topics that will help you get started with testing. I encourage you to continue exploring the official Jest and React Testing Library documentation to gain a deeper understanding of these testing tools and how to use them effectively.

Advantages of Unit Testing and Jest

✔️ Testing is essential for software development in order to assure quality and detect errors.

✔️ Automated testing is more efficient than human testing and can detect problems sooner in the development process.

✔️ Jest is a popular JavaScript testing package, particularly for unit testing in React.

✔️ Unit testing is an important section of the testing pyramid since it focuses on evaluating individual components in isolation.

✔️ Jest simplifies unit testing in React by providing a straightforward API, built-in mocking and code coverage reporting, and parallel test execution.

coma

Conclusion

In this blog, I have demonstrated the importance of unit testing and how Jest can be used to test React applications. We have also seen some of the powerful features that Jest offers. By implementing unit tests, developers can ensure that their code is reliable, maintainable, and scalable. In summary, Jest is an essential tool for any developer looking to build high-quality React applications.

Nadeem K

Associate Software Engineer

Nadeem is a front-end developer with 1.5+ years of experience. He has experience in web technologies like React.js, Redux, and UI frameworks. His expertise in building interactive and responsive web applications, creating reusable components, and writing efficient, optimized, and DRY code. He enjoys learning about new technologies.

Keep Reading

Keep Reading

Mindbowser is excited to meet healthcare industry leaders and experts from across the globe. Join us from Feb 25th to 28th, 2024, at ViVE 2024 Los Angeles.

Learn More

Let's create something together!