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.

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.

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:

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:

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.

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://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