Testing software is as important as developing it, since, testing helps find out whether the software meets the actual requirements or not. A thoroughly tested software product ensures dependability, security, and high performance, which leads to time-saving, customer satisfaction, and cost-effectiveness.
ReactJS is a popular JavaScript library that is used for building highly rich user interfaces. Its popularity can be gauged from the significant increase in the number of downloads over the years as seen below.
A few reasons that make React a popular framework among developers are:
- Ease of Learning: React JS has a short learning curve as compared to other front-end libraries or frameworks, hence any developer with a basic knowledge of JavaScript can start learning and building apps using React JS in a short period of time.
- Quick Rendering: React JS uses virtual DOM and algorithms like diffing, which makes the development process fast and efficient.
- One-way data-binding: React JS follows one-way data binding, which means you get more control over the flow of the application.
- High Performance: The best advantage of React JS is its performance. There are many features that make it possible:
- React uses virtual DOM, which enables the re-rendering of nodes only when they are required to.
- React JS also supports bundling and tree shaking, which minimizes the end user’s resource load.
This tutorial deep dives into performing unit testing of React Apps using JEST.
Table of ContentsWhat is Unit Testing for React Apps? Why is it important?
Unit Testing is a testing method that tests an individual unit of software in isolation. Unit testing for React Apps means testing an individual React Component.
“Unit testing is a great discipline, which can lead to 40% – 80% reductions in bug density.” – Eric Elliotte
Unit Testing is important for React Apps, as it helps in testing the individual functionality of React components. Moreover, any error in code can be identified at the beginning itself, saving time to rectify it at later stages. Some of the core benefits of Unit Testing are:
- Process Becomes Agile: Agile Testing process is the main advantage of unit testing. When you add more features to the software, it might affect the older designs and you might need to make changes to the old design and code later. This can be expensive and require extra effort. But if you do unit testing, the whole process becomes much faster and easier.
- Quality of code: Unit testing significantly improves the quality of the code. It helps developers to identify the smallest defects that can be present in the units before they go for the integration testing.
- Facilitates change: Refactoring the code or updating the system library becomes much easier when you test each component of the app individually.
- Smooth Debugging: The debugging process is very simplified by doing unit testing. If a certain test fails, then only the latest changes that have been made to the code need to be debugged.
- Reduction in cost: When bugs are detected at an early stage, through unit testing, they can be fixed at almost no cost as compared to a later stage, let’s say during production, which can be really expensive.
How to perform Unit testing of React Apps using JEST?
Jest is a JavaScript testing framework that allows developers to run tests on JavaScript and TypeScript code and can be easily integrated with React JS.
Step 1: Create a new react app
For unit testing a react app, let’s create one using the command given below:
npx create-react-app react-testing-tutorial
Open the package.json, and you will find that when you use create-react-app for creating a react project, it has default support for jest and react testing library. This means that we do not have to install them manually.
Step 2: Create a component
Let’s create a component called Counter, which simply increases and decreases a numeric value at the click of respective buttons.
import React, { useState } from "react"; const Counter = () => { const [counter, setCounter] = useState(0); const incrementCounter = () => { setCounter((prevCounter) => prevCounter + 1); }; const decrementCounter = () => { setCounter((prevCounter) => prevCounter - 1); }; return ( <> <button data-testid="increment" onClick={incrementCounter}> + </button> <p data-testid="counter">{counter}</p> <button disabled data-testid="decrement" onClick={decrementCounter}> - </button> </> ); }; export default Counter;
Here, the important thing to note is the data-testid attributes that will be used to select these elements in the test file.
Step 3: Write a unit test for the react component
Before writing an actual unit test, let’s understand the general structure of a test block:
- A test is usually written in a test block.
- Inside the test block, the first thing we do is to render the component that we want to test.
- Select the elements that we want to interact with
- Interact with those elements
- Assert that the results are as expected.
The unit test of react component can be written as seen in the code snippet below:
import { render, fireEvent, screen } from "@testing-library/react"; import Counter from "../components/Counter"; //test block test("increments counter", () => { // render the component on virtual dom render(<Counter />); //select the elements you want to interact with const counter = screen.getByTestId("counter"); const incrementBtn = screen.getByTestId("increment"); //interact with those elements fireEvent.click(incrementBtn); //assert the expected result expect(counter).toHaveTextContent("1"); });
Note: In order to let jest know about this test file, it’s important to use the extension .test.js.
The above test can be described as:
- The test block can be written either using test() or it(). Either of the two methods takes two parameters:
- The first parameter is to name the test. For example, increments counter.
- The second parameter is a callback function, which describes the actual test.
- Using the render() method from the react testing library in the above test to render the Counter component in a virtual DOM.
- The screen property from the react testing library helps select the elements needed to test by the test ids provided earlier.
- To interact with the button, using the fireEvent property from the react testing library in the test.
- And finally, it is asserted that the counter element will contain a value ‘1’.
Step 4: Run the test
Run the test using the following command:
npm run test
Test Result
Mocking Data with Jest
Let’s create a component and fetch some data using Axios. For that first install Axios using the following command:
npm i axios
Create a new component inside the components folder as given below:
import React, { useState, useEffect } from "react"; import axios from "axios"; const Todos = () => { const [todoList, setTodoList] = useState(null); useEffect(() => { (async () => { const todos = await axios.get( "https://jsonplaceholder.typicode.com/todos" ); setTodoList(todos.data); })(); }, []); return todoList ? ( <ul> {todoList.map((todo, index) => ( <li key={index} data-testid=’todo’>{todo.title}</li> ))} </ul> ) : ( <p>Loading....</p> ); }; export default Todos;
The above component is simply rendering a list of todos on the browser. Now in order to test this component, one approach can be that the test function itself makes a call to the endpoint of the API and then tests whether the result obtained is correct or not.
But there are a couple of issues with this approach:
- It can be expensive to create multiple requests.
- Making requests and getting responses can be a slow operation.
- There is an external dependency in the test i.e. Axios.
So the solution to all the above problems is mocking. The purpose of mocking is to isolate the code tested from external dependencies such as API calls. This is achieved by replacing dependencies with controlled objects that simulate those dependencies.
For creating a mock with jest, first import Axios using the following command:
import axios from 'axios'
Then mock it using the below command:
jest.mock('axios')
Then create dummy data that has a similar format to the actual result, and return the mocked value:
const dummyTodos = [ { userId: 1, id: 1, title: "todo 1", completed: false, }, { userId: 1, id: 2, title: "todo 2", completed: false, }, { userId: 1, id: 3, title: "todo 3", completed: false, }, ]; axios.get.mockResolvedValue({ data: dummyTodos});
Here is the complete code in tests/Todos.test.js file:
import { render, waitFor, screen } from "@testing-library/react"; import Todos from "../components/Todos"; import axios from "axios"; jest.mock("axios"); const dummyTodos = [ { userId: 1, id: 1, title: "todo 1", completed: false, }, { userId: 1, id: 2, title: "todo 2", completed: false, }, { userId: 1, id: 3, title: "todo 3", completed: false, }, ]; test("todos list", async () => { axios.get.mockResolvedValue({ data: dummyTodos }); render(<Todos />); const todoList = await waitFor(() => screen.findAllByTestId("todo")); expect(todoList).toHaveLength(3); });
Test Result
After running the test, the result obtained can be seen below:
Code Coverage using Jest
Code Coverage means determining how much code has been executed while running the test. Generating a code coverage with jest is quite simple. If you are using npm, run the below code to get the code coverage:
npm test -- --coverage
With yarn, run the following command to get the code coverage:
yarn test --coverage
The result will look something like this:
Best practices for testing React Apps with JEST
- Avoid unnecessary tests: Consider a test where you expect some element to be defined and then expect it to have some property on it. Now the latter one becomes unnecessary, because what is the point if the element was not defined at all.
- Don’t test implementation details: If your test does something that your user doesn’t, chances are that you are testing implementation details. For example, you may be exposing a private function just to test your component. This is a code smell — don’t do it. A refactor can easily break your test.
- Push business logic into pure functions rather than UI components: Consider, having a Shopping Cart UI component that should not compute the cart total. This should be pushed to a pure function because it is easier to test.
Conclusion
Unit testing is the easiest way to improve the quality of your React applications since it helps in finding bugs and defects in your code. Moreover, the early discovery of code bugs in the SDLC reduces the overall cost of development because less time is spent on bug fixing in the later stage of the project. This leads to overall customer satisfaction and helps in gaining more trustworthy clients.
Once, the unit testing is done, it is suggested to test the application end to end on real devices and browsers for identifying bottlenecks in the user experience. Using a real device cloud, like BrowserStack, allows you to test on 3000+ browser device combinations, under real user conditions.
BrowserStack is compatible with different automation frameworks like Selenium, Cypress, Playwright, Puppeteer, etc. It is also compatible with CI/CD tools like Jenkins, Travis CI, CircleCI, Bamboo, etc. facilitating test automation in Agile Teams to test on real browsers and devices, thus accelerating the software development cycle. It also supports parallel testing, which helps save time by running tests on multiple browsers and devices simultaneously.
Try React App Testing on BrowserStack for Free
ncG1vNJzZmivp6x7o77OsKqeqqOprqS3jZympmeXqralsY6upaKsXamytMDIp55mp5Ziv6atwq1kmqigqHq2v8innmailajB