Nice to meet you!

We're delighted to have you here. Need assistance with our services or products? Feel free to reach out.

Hero Illustration
0 Comments
Mitrais, Software Development

How to Write Unit Tests Using the React Testing Library

Background

Unit tests are small, isolated tests that assess a single function or unit of code. They are typically fast to run, easy to write, and are used to ensure that individual parts of the code are working correctly.

In software development, there is a term called the “Testing Pyramid.” The concept describes different types of tests divided into three layers: the base of the pyramid representing unit tests, the middle layer representing integration tests, and the top layer representing end-to-end tests.

Figure 1. Testing Pyramid

The pyramid shape implies that there should be more unit tests than integration tests and fewer end-to-end tests. The idea is that by having many unit tests, you can catch most of the bugs early in the development cycle and have fewer bugs to catch with integration and end-to-end tests. Furthermore, the bottom layer of the pyramid is fast and cheap, while getting to the top becomes slower and more expensive because it takes longer to run and is more complex to set up. In addition, these tests are more resource intensive due to the integration needs.

Testing Utilities for ReactJS

Writing unit tests can be challenging. One of the challenges is our accessibility to the DOM in web applications. However, we can make it simpler by using testing utilities. There are libraries that could help us write unit tests in ReactJS, like Enzyme and React Testing Library. This article will specifically talk about React Testing Library because it’s easy to use and has been fully integrated with the Create React App (CRA) tool. Therefore, we don’t need to set up from scratch. However, if you don’t use CRA, you can set it up manually by following this guide in their official documentation.

React Testing Library has claimed that they offer lightweight solutions for testing. Bundle Phobia supports their claim, as seen in the image below.

Figure 2. Bundle Phobia Analysis

Implementation

Create a Simple ReactJS Component

In this case, we’ll create a simple button component that has a few properties:

  • Label: to show the button label.
  • Color: to decide which color scheme we will use.
  • Disabled: to disable button if needed.
  • onClick: to give an action if the button is clicked.

We will not have a detailed implementation. Instead, we will focus on the unit test itself.

To start, create a new project using the create-react-app command:

npx create-react-app write-unit-test --template typescript

Once the project has been created, create a new button component in src/components/Button/Button.tsx

interface IButtonProps {
  label: string;
  color?: 'primary' | 'secondary' | 'default' | undefined
  disabled?: boolean | undefined
  onClick?: () => void;
}
 
export default function Button({label, color = 'default', disabled, onClick}: IButtonProps) {
  const getColorClassName = () => {
    if (color === 'primary') {
      return 'button-primary'
    } else if (color === 'secondary') {
      return 'button-secondary'
    } else {
      return 'button-default'
    }
  }
 
  return <button className={getColorClassName()} disabled={disabled} onClick={onClick}>{label}</button>
}

Create a Simple Unit Test File

We’ll start the unit test by creating a simple test case. So, you can write and run it to ensure the unit test is correct.

Our project structure will look like this:

Figure 3. Project Structure

Notice that we have folder __test__ and Button.test.tsx inside it. That is how we structure our test code in a React application. This structure makes the unit test code easier to maintain because it is just one level below the component file. In addition, we name the unit test file like that because, by default, the test runner will run the file with pattern [component].test.tsx.

Now, add file src/components/Button/__test__/Button.test.tsx and import the necessary file and library.

import { render, screen } from "@testing-library/react"
import Button from "../Button"

Create a simple script to test if your component was rendered correctly.

describe('Button', () => {
  it('should render Button', () => {
    // it will render the button when the test runner runs this test case
    render(<Button label="label"/>)
    // it will check if the label text is in the document
    expect(screen.getByText('label')).toBeInTheDocument()
  })
  // another test cases
})

Notice that we use screen.getByText to check whether the component is in the DOM. To check for other queries that you can use, look at this documentation.

Now that we have written the test case, let’s try to run the unit test by using this command:

npm run test

Once it is successful, you will receive a pass, as shown in the following image:

Figure 4. Test Result

Add More Test Cases

Now that we have created a simple test case, we will add more scenarios to the test. The other scenarios are:

  • Button clicked.
  • Button disabled.
  • Button applied to the styles.

First, we must import userEvent to simulate an interaction with the DOM, such as clicking a button or inputting a field. You can see the complete list in this link.

import userEvent from '@testing-library/user-event'

Then, input the following code into the // another test cases block.

it('should call click handler', async () => {
    // this is a mock function
    const clickHandler = jest.fn()
    render(<Button label="label" onClick={clickHandler}/>)
 
    // make sure the mock function is not called
    expect(clickHandler).not.toBeCalled()
 
    const button = screen.getByText('label')
 
    // simulate click on the button
    await userEvent.click(button)
    
    // make sure the mock function is called
    expect(clickHandler).toBeCalled()
  })
 
  it('should be disabled', async () => {
    render(<Button label="label" disabled/>)
 
    // make sure the button is disabled
    expect(screen.getByText('label')).toBeDisabled()
  })
 
  it('should have default style', async () => {
    render(<Button label="label"/>)
 
    // make sure the button has default style
    expect(screen.getByText('label')).toHaveClass('button-default')
  })
 
  it('should have primary style', async () => {
    render(<Button label="label" color="primary"/>)
 
    // make sure the button has primary style
    expect(screen.getByText('label')).toHaveClass('button-primary')
  })
 
  it('should have secondary style', async () => {
    render(<Button label="label" color="secondary"/>)
 
    // make sure the button has secondary style
    expect(screen.getByText('label')).toHaveClass('button-secondary')
  })

Try to run the test and check if it has passed all scenarios.

Figure 5. Test Result with More Scenarios

Conclusion

Problems or bugs in the systems can be identified in the preliminary stages of unit testing to reduce the cost of bug fixing. If these bugs are discovered later, fixing them can be much more expensive. Therefore, it makes unit testing essential to write.

React Testing Library offers a powerful and efficient way to write unit tests for your React components. By following the step-by-step instructions outlined in this article, you can easily incorporate these tests into your development process and catch errors early on. By doing so, you will be able to improve the overall quality of your code and reduce maintenance costs. Whether you’re a seasoned developer or just starting out, the React Testing Library is great to have in your toolkit.

To find out more, please check their official documentation here: https://testing-library.com/docs/react-testing-library/intro

Full code: https://github.com/alimuddinhasan/hello-react-testing-library

References:

https://testing-library.com/

https://www.headspin.io/blog/the-testing-pyramid-simplified-for-one-and-all

https://www.guru99.com/unit-testing-guide.html

Author: Alimuddin Hasan Al Kabir, Software Engineer Analyst Programmer

Contact us to learn more!

Please complete the brief information below and we will follow up shortly.

    ** All fields are required
    Leave a comment