Testing React components using Jest

Introduction

Jest is a testing framework, built on top of Jasmine, with added features such as parallel execution, zero startup configuration, snapshot testing capability and extended capabilities like more matchers for expect, mock, spies.

Let us see how can we get started with testing React components using Jest

Installing Jest

If you have created the React Application using Create React App, then jest comes preinstalled. The below instructions are for the custom React Application development, which do not have Jest pre installed.

In order to install Jest, add the Jest package in development dependency.

npm install jest --save-dev

If you are using yarn, then run the command

yarn add jest --save-dev

Now that you have jest installed, add the script for test, so that it is easier to test.

{
    ....
    "scripts": {
        "test": "jest"
     }
    ....
}

By adding the above script, you can easily run the test cases.

npm test

Sample Application

We are going to use the sample application created using Create React App. You can browse the project at github (master branch).

We are going to write a simple React component which has following functionality.

  • On click of the button, it will display a message on the page.
  • By default, the message will be hidden.

Below is the component called App.js

import React, { Component, Fragment } from 'react';

class App extends Component{
  state = {
    warning: false
  }

  showWarning = () => {
    this.setState({
      warning: true
    })
  }

  render(){
    const { warning } = this.state;
    return (
      <Fragment>
        { 
          warning ? <span data-testid="warning-text">This is warning text</span> : null
        }
        <button data-testid="warning-button" onClick={this.showWarning}>Show Warning</button>
      </Fragment>
    )
  }
}

export default App;

Writing first unit test case

Create React App comes with React testing library by default. React testing library is a helper library, that makes it easier to write the test case for React components.

Create a file called App.test.js, and it will be picked up by default by Jest for testing. As mentioned above, we are going to write test case for two main scenarios

  • By default, the message will be hidden.
  • On click of the button, it will display a message on the page.

Here is the App.test.js file

import React from 'react';
import { render } from '@testing-library/react';
import App from './App';

test('do not show warning by default', () => {
  const wrapper = render(<App />);
  expect(wrapper.queryAllByTestId('warning-text')).toHaveLength(0);
})

We have imported the render method from React testing library. The render method returns a object that contains various helper methods to query on the DOM.

We get the reference to the render using the following code

const wrapper = render(<App />);

Then we check that warning text should not be shown by default. There are many helper methods available like query, find and get. We are going to use the queryAllByTestId method, which will allow you to query the DOM using the testid attribute. It returns the array of the matching dom elements.

expect(wrapper.queryAllByTestId('warning-text')).toHaveLength(0);

So we have seen how to test the default (or initial) scenario. Let us test the second scenario

  • On click of the button, it will display a message on the page.
test('show warning on click of button', () => {
  const wrapper = render(<App />);
  wrapper.getByTestId('warning-button').click();
  expect(wrapper.queryAllByTestId('warning-text')).toHaveLength(1);
})

In the above test case, we render the App component just like the first test case. Then we get the button element and click on it to show the warning message.

wrapper.getByTestId('warning-button').click();

On click of the button, the warning message will be shown. So we are going to test it by checking that the length of the arrat of matching element is 1.

expect(wrapper.queryAllByTestId('warning-text')).toHaveLength(1);

Mocking the component

Till now, we have used Jest only to run the test cases. Jest provides much more functionality to test the applications. One of the major feature is mocking and spies. Jest allows you to mock the components which are not required to be tested (Jest is framework agnostic, but for this tutorial, we are going to focus only on the React component).

Now go to branch mock.

https://github.com/we-techdomain/testing-react-component-jest/tree/mock

Here we have moved the warning message to a separate component called Warning. In this tutorial, we are going to demonstrate

  • How to test App component which contains the child component Warning by mocking the child component.

We are going to test

  • By default Warning component should not be visible
  • On click of the button, Warning component should be visible

This is the code for App.js component.

import React, { Component, Fragment } from 'react';
import Warning from './Warning';

class App extends Component{
  state = {
    warning: false
  }

  showWarning = () => {
    this.setState({
      warning: true
    })
  }

  render(){
    const { warning } = this.state;
    return (
      <Fragment>
        { 
          warning ? <Warning/> : null
        }
        <button data-testid="warning-button" onClick={this.showWarning}>Show Warning</button>
      </Fragment>
    )
  }
}

export default App;

Notice the below line, here we have replaced the warning div from previous tutorial with the Warning component.

{ 
     warning ? <Warning/> : null
}

In order to test the above component, we will have to mock the Warning component. We can test the component without mocking Warning component also, but it will be brittle test. Because we are unit testing App component, its test case should not be dependant upon the implementation of Warning component. We have to try write test case in such a way that if implementation of child component changes, the parent component test cases do not fail. That will make the test case robust.

Jest provides methods to mock the components. We are going to go indepth into mocking the modules, but will show a sample of one way of manual mocking the ES6 modules.

In order to mock the Warning component, we are going to use the manual mocks. That means that we are going to provide our own simple and consice mock implementation of it. In order to mock the component, jest provides the following method

jest.mock("modulepath", mockFunction)

Our module path is going to be ./Warning, because the Warning component is present at that path relative to the test case file.

In the mock function, we are going to use jest.fn().mockImplementation to mock the implementation of Warning component. The code is going to be

jest.mock('./Warning', () => {
  return jest.fn().mockImplementation(() => 
    <div data-testid="warning-text">Mock Warning Component</div>);
})

Inside the mockImplementation function, we are passing a custom mock React component with the data-testid attached, so that we can use the same test cases from the previous scenario (first unit test case).

Due to this, the other test cases are same as first unit test case.

App.test.js

import React from 'react';
import { render } from '@testing-library/react';

import App from './App';

jest.mock('./Warning', () => {
  return jest.fn().mockImplementation(() => 
    <div data-testid="warning-text">Mock Warning Component</div>);
})

test('do not show warning by default', () => {
  const wrapper = render(<App />);
  expect(wrapper.queryAllByTestId('warning-text')).toHaveLength(0);
})

test('show warning on click of button', () => {
  const wrapper = render(<App />);
  wrapper.getByTestId('warning-button').click();
  expect(wrapper.queryAllByTestId('warning-text')).toHaveLength(1);
})

The mock functions are automatically hoiseted on top, so there is no difference by importing App component before or after the mock code. Importing App after the mock will result in eslint error (import/first), so its better to import App first.

Mocking the timers

We have timer functions inside our code. Suppose that we have a timeout of 10 seconds in our code, then writing the test case which will wait for 10 seconds to do the assertion will result in delay of finishing the test case.

Thankfully, Jest has a solution for this. It provides fake timers, which can be used to speed upon the time. Refer to the tutorial below to learn more.

How to unit test the timer functions – setTimeout and setInterval in React using jest

Mocking the http api async code

We already have detailed tutorial to understand the mocking of async code like http calls. Please refer to the tutorials below.

How to write unit test cases for asynchronous code Axios or fetch library in React using jest and enzyme

https://techdoma.in/react-js-testing/testing-async-http-code-axios-or-fetch-using-enzyme-vs-react-testing-library/

Mocking the 3rd party libraries using only Jest

Axios is a popular library that is used to make the http calls. It is possible to use jest to mock this library also. This guide will serve as an example on how to mock any 3rd party library module

https://techdoma.in/react-js-testing/how-to-mock-the-axios-library-in-jest-without-mock-adapter-or-library/