A complete guide for testing in React using Jest along with code coverage report

A complete guide for testing in React using Jest along with code coverage report

Writing test cases is an important part of React development. Each component should have a test case written correctly so by just running a single command we can make sure that our entire application works correctly no matter how big the application is.

So if some API call is failing because the server is down or there is some issue processing request we can easily catch it before the application is deployed to production by just running a single command.

Therefore writing unit tests in React is very important and every React developer should know how to write the test cases.

In this article, we will explore all of that.

So let’s get started

To start with, clone the todo list repository code from HERE which is created to demonstrate localStorage in this article

Once cloned, run the application by executing the following commands:

  • npm install
  • npm start

Now we will start writing test cases using Jest Framework.

When using create-react-app, jest is already included so there is no need of installing it separately as in our case.

We will write a simple test case to understand jest and then we will start writing test cases for the todo app.

Create a new folder named tests inside src folder and add a simple test file with the name app.test.js with following code:

// app.test.js
const add = (a, b) => a + b;

test('should add two numbers', () => {
 const sum = add(3, 4);
 expect(sum).toBe(7);
});

Note: Every test file should end with .test.js extension. This is how jest is able to find all the test files to be executed.

Let’s understand the above code.

Here we are testing add function which takes two numbers and returns the sum of those numbers. Jest provides some global functions one of them is testfunction inside which we write our test case.

You can find a complete list of global functions HERE

The test function takes two arguments.

  1. the string which describes what we are testing
  2. the function where we write our test case_

Inside the test function, we are calling the add method by passing two numbers and then checking if the sum of 3 and 4 equals 7.

To run this file, open the command line or terminal and execute the following command

npm run test

Once executed, you will see that our test case is passed

Image for post

Now change the expect statement from

expect(sum).toBe(7);

to

expect(sum).toBe(8);

and you will immediately see that the test case has failed because 3 + 4 is definitely not 8 and you will see the filename along with the line number where it fails. That’s how easy to test and debug with jest.

Image for post

toBe is one of the many methods provided by Jest for testing. You can find the complete list HERE Change the toBe value from 8 to 7 to make it work again.

Now, let’s start testing our todo list app. To do that we need to install the enzyme library and make some configuration changes.

To easily test components and handle the event handlers, manage state, etc, Enzyme library is used together with Jest.

Install enzyme and it’s dependencies using

npm install --save-dev enzyme@3.11.0 enzyme-adapter-react-16@1.15.2 jest-enzyme@7.1.2

If you are using react less than version 16, you need to install its compatible version of the adapter as shown HERE

To use Enzyme with Jest, create a new file setupTests.js inside the src folder and add the following content

import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import 'jest-enzyme';

configure({ adapter: new Adapter() });

To connect it with Jest create a file with the name jest.config.json in the root of the project where package.json file is present and add the following content

{
 "setupFiles": ["<rootDir>/src/setupTests.js"]
}

Now we are done with the configuration, let’s start testing the Header component from src/components directory.

Snapshot Testing

Snapshot testing is a way to test react components where we create a snapshot of the component by passing the props if any.

It’s usual practice to create the same folder structure for test files as components inside the src folder so create components folder inside src/tests folder and create a new file Header.test.js inside it and add the following code

import React from 'react';
import { shallow } from 'enzyme';
import Header from '../../components/Header';

test('should test Header component', () => {
 const wrapper = shallow(<Header />);
 expect(wrapper).toMatchSnapshot();
});

Here, we are using shallow rendering to create a snapshot of the Header component.

Now, if you run the tests again using

npm run test

you will see that all tests passed and a new folder with the name __snapshots__is created inside components which has the Header component snapshot saved to the file

Image for post

Now open the src/components/Header.js file and change something in the file like adding an exclamation mark in h1

const Header = () => {
 return <h1>Todo List App!</h1>;
};

export default Header;

Now, if you save the file, you will see an error as shown below

Image for post

The error clearly tells us that, the new snapshot with the exclamation mark does not match the original snapshot saved in Header.test.js.snap.

Here we have two choices

  1. Keep the change we did in Header.js (added exclamation mark)
  2. Discard the change in Header.js

So if you press w key in the terminal you will see more options

Image for post

If we want the change in Header.js, press u key and the snapshot will be updated with our changes in Header.js file and you will see the updated snapshot message

Image for post

If you don’t want the exclamation mark in the Header.js, remove it from the file, and then save the file which will re-run the tests and will keep the old snapshot without the exclamation mark.

So using snapshot testing we can easily test our React components and track if something changes in any of the files may be by mistake or by purpose.

Now, let’s write test cases for src/components/TodoLists.js component.

Create a new file TodoLists.test.js inside src/tests/components folder

The TodoLists.js component accepts prop which is an array of todos, So let’s write a test case which takes an array as props

import React from 'react';
import { shallow } from 'enzyme';
import TodoLists from '../../components/TodoLists';

test('should test TodoLists component with default state of empty array', () => {
 const wrapper = shallow(<TodoLists todos={[]} />);
 expect(wrapper).toMatchSnapshot();
});

If your npm run test command is still running, you can see a new snapshot is created into __snapshots__ folder

Image for post

So you can see that the empty array check from TodoLists.js is correctly getting executed.

Now, we will write another test case by adding some todos

test('should test TodoLists component with list of todos', () => {
 const wrapper = shallow(
  <TodoLists todos={['Read Newspaper', 'Publish Article']} />
 );
 expect(wrapper).toMatchSnapshot();
});
Image for post

As you can see the snapshot is updated with the new test case data

Image for post

Now we have handled both the conditions from TodoLists.js

Let’s move on to the next component which is TodoItem.js component

It accepts a single todo as a prop

Create a new file TodoItem.test.js inside src/tests/components folder and add the following code inside it.

Image for post Image for post

Let’s start with testing AddTodo.js component now

Create a new file AddTodo.test.js inside src/tests/components folder

AddTodo component accepts a handleAddTodo method as a prop

Let’s test AddTodo component by not passing any props

Image for post

when you run npm run test you will see a new snapshot file created for this

Image for post

Now let’s add another test case to handle the onSubmit method call

When we submit the form, handleAddTodo method is called so we will be creating a spy in jest to test that function call.

Creating a spy is a way of creating a function and checking if the written method gets called or not.

To create a spy for function in Jest, we use jest.fn method provided by Jest and we will pass it as the handleAddTodo prop

Then we will find the Form element and we will trigger the onSubmit handler.

It’s always good to save the snapshot before, so it will be easy to identify what is the final element we need to use in .find method.

we will use the simulate method of the enzyme by mentioning which event to trigger.

Once the submit event is triggered, we will verify if it’s actually triggered or not by calling toHaveBeenCalled method of Jest

Image for post

You can find all other methods provided by enzyme HERE

If you run the tests, you can see that a new snapshot is saved and our test runs successfully.

Image for post

Feel free to open the AddTodo.test.js.snap file and understand what gets stored when we create a new snapshot for this component which is a very important part of writing test cases.

Now we will add tests for the final component which is TodoList.js

Create a new file TodoList.test.js inside src/tests/components folder

Image for post

Its snapshot will look like this

Image for post

Now, we are done with all component test cases. Let’s explore how to see the total code coverage for the entire application.

For this, we will need to make some configuration changes.

Open jest.config.json and add the following content:

{
 "setupFiles": ["<rootDir>/src/setupTests.js"],
 "testRegex": "/*.test.js$",
 "collectCoverage": true,
 "coverageReporters": ["lcov"],
 "coverageDirectory": "test-coverage",
 "coverageThreshold": {
  "global": {
  "branches": 0,
  "functions": 0,
  "lines": 0,
  "statements": 0
  }
 },
 "moduleDirectories": ["node_modules", "src"]
}

Here we are telling jest to generate coverage reports for all files ending with .test.js as shown in testRegex property.

Now to generate coverage report, execute the following command from the terminal

npm run test -- --coverage --watchAll=false

Once executed, we will see the total coverage for each test file

Image for post

This will also create a new coverage folder in the project

If you open the index.html file in the browser located at coverage/lcov-report folder you will be able to view the report

Image for post

As you can see, we have covered all the scenarios for each of the components except TodoList.js for which it’s showing only 29.41% statements covered.

Let’s open the TodoList.js and we can see what extra test cases we need to handle

Image for post

As you can see, the lifecycle methods and handleAddTodo method is not handled in our test cases so let’s add the test cases for them

Open TodoList.test.js from src/tests/components folder and add the following test case in it.

test('should call handleAddTodo method', () => {
 const wrapper = shallow(<TodoList />);
 const instance = wrapper.instance();
 const value = 'Publish Article';
 instance.handleAddTodo({
  preventDefault: () => {},
  target: {
   todo: {
     value
    }
   }
 });
});

Here we are manually calling the handleAddTodo using instance.handleAddTodo method. The handleAddTodo is expecting an event object as a parameter which has preventDefault method and value property so we are providing it as a parameter

Image for post

Now run the coverage command again

npm run test -- --coverage --watchAll=false

and you will see the coverage percent has increased for TodoList.js

Image for post

If you open the TodoList.js file, you will see that handleAddTodo method is covered but the componentDidMount is still not covered by test cases because of which the percentage is 88.24% and not 100%

Image for post

I will leave it to you to add the test case for it as an exercise for you.

Hint: you can use the _mount_ method of the enzyme to handle that test case

A couple of improvements:

If you open the TodoList.test.js file, you can see that we have duplicate code of const wrapper = shallow(); It’s repeated in the first and second test case.

If we add more test cases it will be repeated again. So jest provides some life cycle methods using which we can remove the duplicate code.

Life cycle methods:

  1. afterAll: It will be executed after all the test cases from the test file are finished executing
  2. beforeAll: It will be executed before executing any of the test cases in a file
  3. afterEach: It will be executed after each of the test cases in a file
  4. beforeEach: It will be executed before each of the test cases in a file_

Complete List: https://jestjs.io/docs/en/api

we will use the beforeAll life cycle method to fix the duplicate code

Image for post

That looks much better now.

You can find complete source code for this application here.

That’s it for today. Hope you learned something new today.

I hope you've been enjoying my articles and tutorials I've been writing. If you found them useful, consider buying me a coffee! I would really appreciate it.

Don’t forget to subscribe to get my weekly newsletter with amazing tips, tricks, and articles directly in your inbox here.

Did you find this article valuable?

Support Yogesh Chavan by becoming a sponsor. Any amount is appreciated!