TDD in React using Jest — beginner tutorial
Tags: react, jest, tdd, mock
Published: 2020-04-13 14:10:25

---
title: TDD in React using Jest — beginner tutorial
published: true
description: Mocking, UI testing, Snapshot testing and more
tags: React, Jest, TDD, Mock
---

# Overview
In this tutorial we’ll get started with using Jest library to test react application. This tutorial will cover following topics
 - Setup react project which includes jest library
 - How to write test using jest
 - Some common jest matchers
 - Concept of mocking and how to do it using jest
 - UI testing of react using react testing library
 - Finally I will also add reference where you can get in depth knowledge

For grasping above topics we’ll create a demo application which lists restaurants which can be filtered by distance from a center location. **We’ll use TDD approach to build this application and give you simple exercise along the way to play with.**

![Jest Tutorial Screenshot](https://dev-to-uploads.s3.amazonaws.com/i/v2ku6b0s1qusb6xh2x7y.gif)

# Prerequisite
You need to
 - be familiar with javascript
 - have some understanding of react like (JSX, Function based components, few hooks like useState, useEffect, useMemo). I will try to explain them as we use it

> The codes are in [Github](https://github.com/devbkhadka/jest-tutorial) with **separate branch for each step**. You can directly jump into any branch and play around.

> If you are familiar with setting up react app you can directly skip to “[List Restaurants](https://medium.com/p/2925db75fb30/)”

# Setup New React Project
> You need [nodejs](https://nodejs.org/en/download/package-manager/) installed before you can continue

 - Create a new folder named “jest-tutorial” and cd into that folder

```
cd /path/to/jest-tutorial
```
 - Run “create-react-app” command

```
# if create-react-app doesn't exists npx will install it and run
npx create-react-app .
```
 - Now you can run your app in browser. You should see a spinning react native logo in browser

```
npm start
```
 - press “ctrl+c” to stop the server in terminal

## Lets Check Some Important Files
 - **package.json** — below is a part of the package json file. It lists project dependencies and commands that you can run

``` json
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
```
 - index.js — It is entry point for the app, it mounts the “App” component to element with id “root” in “public/index.html” file

``` jsx
ReactDOM.render(
<React.StrictMode>
   <App />
</React.StrictMode>,
document.getElementById('root')
);
```
 - App.js — It is the root component for our application. We can think of a react application as a tree where “App” component is root and it and its descendants can have one or more components as branches.

``` jsx
import './App.css';
function App() {
return (
<div className="App">
    ...
</div>
);
}
export default App;
```

### Some Explanations
 - It imports “./App.css” as a global css file
 - “App” function returns JSX which is HTML like syntax in Javascript ([What is JSX?](https://reactjs.org/docs/introducing-jsx.html))
 - It exports “App” component to be used in other files

## Basic Layout
 - Replace content of “App.css” file
 - replace whole content of App.css file with css in following gist. This css includes basic styling for our demo application.

``` css
.App {
  display: flex;
  flex-direction: column;
  height: 100vh;
  color: white;
  overflow: hidden;
}

.App-header {
  background-color: #282c34;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
  border-bottom: 1px solid rgb(143, 143, 143, 30);
}

.App-content {
  padding-top: 16px;
  background-color: #40444d;
  display: flex;
  flex-direction: column;
  flex-grow: 1;
  overflow: scroll;
}

.App-content form{
  margin: 16px 10% 16px 10%
}

.App-content input {
  box-sizing: border-box;
  width: 100%;
  height: 32px;
  font-size: 20px;
  padding: 4px;
}


.App-content ul {
  box-sizing: border-box;
  margin: 16px 10% 16px 10%;
  padding: 0px;
}

.App-content li {
  box-sizing: border-box;
  width: 100%;
  font-size: 20px;
  padding: 16px;
  list-style-type: none;
  background-color:  #282c34;
  border-bottom: 1px solid rgb(143, 143, 143, 30);
}

.App-link {
  color: #61dafb;
}
```

- Replace the JSX in “App.js”
replace all JSX content (<div className=”app”> and its contents) with following

``` jsx
<div className="App">
    <header className="App-header">
        <h2>Welcome to Jest Tutorial</h2>
    </header>
    <div className="App-content">
    </div>
</div>
```

# List Restaurants
Lets start by listing restaurants in UI. For that we need list of restaurants, which we may need to fetch from an api and then display it in UI. It sounds bit complex, if we try to implement all the functionality at once it will be complex to implement and hard to debug.

> In TDD we build application in small increments. We first plan the next incremental feature to be implemented, then we write test code to validate that the implementation works as expected, with some given preconditions and inputs. Then only we write code for the feature

## App Component
Start here by checking-out “[1-skeleton](https://github.com/devbkhadka/jest-tutorial/tree/1-skeleton)” branch

### Implementation Steps
We’ll implement the “List Restaurants” feature in following steps
 - Instead of directly showing list in “App” component we’ll create “Restaurants” component which will be included in “App” component. This will separate the responsibility and make it more testable.
 - “Restaurants” component will take list of restaurants as input and display it

### Test Cases for App Component
Now lets write test cases for above steps.

```
App Component
    - Should call "fetchRestaurants" function to get restaurants
    - Should render "Restaurants" component with result from "fetchRestaurants"
```

Lets write the first unit test, for that lets create a “__tests__” folder in “src” and move “src/App.test.js” in it. It is common practice to put tests under “__tests__” folder.

Now replace content of “App.test.js” with following code

``` javscript
import React from 'react';
import { render } from '@testing-library/react';
import App from '../App';
describe("App Component", ()=>{
    it('Should call "fetchRestaurants" function to get restaurants', ()=>{
        fail("not implemented")
    })
})
```

### Some explanation
 - “npm test” runs the jest command, which will look for js files inside __tests__ or *.test.js or *.specs.js files and runs tests inside it one at a time in not particular order
 - “describe” is function provided by jest which will be available without import when running test with jest. It is used to group similar tests.
 - “it” is also function available in test environment it represents a single test case. Here we intentionally wrote test to fail.

### Command to Run Test

```
npm test
```
it should show result ‘Failed: “not implemented”’ in the console

### Using Mock for Testing
 - If you notice, the test above depends on a function called “fetchRestaurants”.
 **Do we have to implement the function first? No, here is why**
 - If we try to implement another functionality while working on one it will complicate things, which is against TDD principals
 - If we use real “fetchRestaurants” in test then when “fetchRestaurants” fails in future, testing depending on it will also fail. It will make pin-pointing the problem harder

**So what is the solution for it?**

Solution is to make a fake “fetchRestaurants” function which will return the value we need for testing, this is called mocking.

Lets see it in action

``` javascript
import React from 'react';
import { render } from '@testing-library/react';
import App from '../App';
import Restaurants from '../Restaurants'
import {fetchRestaurants} from '../utils'
import * as fixtures from '../fixtures'
import { act } from 'react-dom/test-utils';

// First mock whole '../Restaurants' and '../utils'
// By default it will mock all the functions in module to return undefined
jest.mock('../Restaurants')
jest.mock('../utils')

// Provide fake return values for the functions
Restaurants.mockReturnValue(null)
// we want fetchRestaurants to return promise that resolves to fixtures.dummyRestaurants
fetchRestaurants.mockResolvedValue(fixtures.dummyRestaurants)

describe("App Component", ()=>{
  
  // function passed to before each is called before running each test
  // It is used to setup pre-condition for each test
  beforeEach(()=>{
    // mockClear clears call history of the mock function
    Restaurants.mockClear()
    fetchRestaurants.mockClear()
  })

  it('Should call "fetchRestaurants" function to get restaurants', async ()=>{
    await act(async () => {
      render(<App />)
    })
    expect(fetchRestaurants).toBeCalled()
  })

  it('Should render "Restaurants" component with result from "fetchRestaurants"', async ()=>{
    await act(async () => {
      render(<App />)
    })
    expect(Restaurants.mock.calls[1][0]).toEqual({list: fixtures.dummyRestaurants})
  })
})
```

### Some Explanations
 - “jest.mock(modulepath)” will modifies the original model by hooking into the import functionality. This is called monkey patching. Any other modules imported in this test file will also see the modified module.
 - So when “App” component see “Restaurants” component in its JSX it will use mock “Restaurants” instead of real one. This gives us chance to monitor how it is being used, like what property being passed.
 - “render” function renders the components in a virtual DOM implemented by “jest-dom” so that the test can be run without a browser
 - We need to wrap render inside “async act(async ()=>{})” because we are updating state in useEffect function which will update state and trigger UI update
 - “expect” function gives us access to variety of matcher that can be used to check if certain condition is satisfied in test.

### Steps to Make the Tests Pass
At this point your test will fail, to make the test to pass you have to do following changes step by step which will take your test little further in each change
 - Create file “src/Restaurants.js ” and add code below

``` javascript
export default function Restaurants() {
}
```

 - create file “src/utils.js” and add code below

``` javascript
export function fetchRestaurants() {
}
```

 - create file “src/fixtures.js” and add code below

``` javascript
export const dummyRestaurants = "Dummy Restaurants"
Add code below before “return” in App.js. Don’t forget to import “fetchRestaurants” in the file
useEffect(()=>{
    fetchRestaurants()
})
```

 - change App function in App.js to look like below. Don’t forget to import “Restaurants”

``` javascript
import React, { useEffect, useState } from 'react';
import './App.css';
import { fetchRestaurants } from './utils';
import Restaurants from './Restaurants';

function App() {
  const [restaurants, setRestaurants] = useState(null)
  useEffect(()=>{
    fetchRestaurants()
      .then(setRestaurants)
      .catch(()=>console.log("error in fetching"))
  }, [])

  return (
    <Restaurants list={restaurants}/>
  );
}

export default App;
```

### Some Explanations
 - callback of “useEffect” is called before each render of App component if values in second parameter changed. Values in second parameter must be a prop or state, empty array means it will run for 1st time only.
We are calling “fetchRestaurants” before each render and calling “setRestaurants” function with value resolved by promise to update restaurants. This will re-render Restaurants component by updating list prop
 - You tests should pass now. Now lets move on to testing “Restaurant Component”

> **Exercise:** Add a test case to do snapshot test of the component. You can get some detail here

**Hint**: Object returned by render function will have “baseElement” property. you can call “expect(baseElement).toMatchSnapshot()” which will create snapshot of html rendered for first time and test “baseElement” against the saved snapshot from next time. It will prevent accidental change in UI.
> **Exercise:** Handle error case of fetchRestaurants.

**Hint:** Resolve object with structure {data: …} for success and {error: …} for error and check condition App component to show or hide error message element

## Restaurants Component
> Start here by checking-out “[2-App-Component](https://github.com/devbkhadka/jest-tutorial/tree/2-App-Component)” branch

### Implementation Steps for Restaurants Component
 - Restaurants component will receive restaurant list as “list” prop and render it by looping through each restaurant
 - It will take distance in a input field and filter the restaurants within the distance. To implement this feature we need a function to calculate distance, which is not implemented yet, so for doing the test we need to mock it.

### Test Cases for Restaurants Component

```
Restaurants Component
    - should render restaurants passed to it
    - should be able to filter restaurants by distance from the center
```

The test cases should look like shown below

``` javascript
import React from 'react'
import {render, fireEvent} from '@testing-library/react'
import Restaurants from '../Restaurants'
import * as fixtures from '../fixtures'
import {calculateDistance} from '../utils'

jest.mock('../utils')
describe("Restaurants Component", ()=>{
    it("should render restaurants passed to it", ()=>{
        // render function returns a handle 
        const {getAllByText} = render(<Restaurants list={fixtures.dummyRestaurants}/>)
        // get elements matching regex
        expect(getAllByText(/Restaurant\d/).length).toBe(5)
    })
    
    it("should be able to filter restaurants by distance from center", ()=>{
        const {queryAllByText, getByTestId} = render(<Restaurants list={fixtures.dummyRestaurants}/>)

        // following block set five different return value for five calls to calculateDistance
        calculateDistance
            .mockReturnValueOnce(30)
            .mockReturnValueOnce(110)
            .mockReturnValueOnce(80)
            .mockReturnValueOnce(60)
            .mockReturnValueOnce(300)

        const inpDistance = getByTestId('inpDistance')
        // fire change event on inpDistance to set distance
        fireEvent.change(inpDistance, {target:{value: 100}})

        expect(queryAllByText(/Restaurant\d/).length).toBe(3)
    })
})
```

### Some Explanation
In short, we interact with rendered DOM using handle returned by “render” function. We can also fire different event on DOM element by using “fireEvent” object. Like we used “change” event to trigger filter and check that list is filtered . More details are on comments in code.

### Steps to Make Test Pass
- Enter code below to “Restaurants.js” file for layout

``` jsx
import React from 'react'
export default function Restaurants({list}) {
   return <div className="App">
        <header className="App-header">
            <h2>Restaurants</h2>
        </header>
        <div className="App-content">
        </div>
    </div>
}
```

 - Create “distance” state by adding following line above “return”
const [distance, setDistance] = useState(null)
 - Add the code block below before “return” line in “Restaurants” function. It will create a memorized value “filteredList” which is changed when either “list” or “distance” state changes

``` javascript
const filteredList = useMemo(()=> {
    return filterWithinDistance(list, distance)
}, [list, distance])
```

 - To render “filteredList” insert code below inside “App-content” div in JSX. This should make first test pass

``` jsx
{
    filteredList && filteredList.map((restaurant, i)=>
        <li key={restaurant.id}>{restaurant.name}</li>
    )
}
```
 - In “utils.js” add following function

``` javascript
export function calculateDistance(location){
}
```

 - Add “filterWithinDistance” function below the “Restaurants” function at the bottom of page. Don’t forget to import “calculateDistance” from “utils”

``` javascript
function filterWithinDistance(restaurants, distance) {
    return distance?
        restaurants.filter(restaurant=> calculateDistance(restaurant.location) <= distance):
        restaurants
}
```
 - Now add the following “form” in JSX above “ul” element

``` jsx
<form onSubmit={(e)=>e.preventDefault()}>
    <input onChange={(e)=> setDistance(e.target.value*1)}
        data-testid="inpDistance"
        placeholder="Enter distance in meters"/>
</form>
```

Now all of your tests should pass.

> Excercise: Handle the case when restaurant list is empty or null by showing a message in UI

**Hint:** In test, render “Restaurant” component with list property “null” and “[]” then verify that you can find element containing the message text. In “Restaurant” component, conditionally show message or list based on “list” prop

> Excercise: Show calculated distance in each restaurant list item.

**Hint:** modify “filterWithinDistance” to return restaurants with calculated distance and show it in UI. In test verify that mocked distance is show in the rendered UI

## Implement “fetchRestaurants”
> Start here by checking-out “[3-Restaurants-Component](https://github.com/devbkhadka/jest-tutorial/tree/3-Restaurants-Component)” branch

### Test Cases for fetchRestaurants

```
fetchRestaurants
    - should call fetch api with correct parameters
    - should return response on fetch success
    - should return empty array on fetch error
```

The test codes should look like

``` javascript
import {fetchRestaurants, RESTAURANTS_URL} from '../utils'
import * as fixtures from '../fixtures'


jest.spyOn(global, 'fetch')

describe('fetchRestaurants', ()=>{
    beforeEach(()=>{
        global.fetch.mockClear()
        global.fetch.mockResolvedValue({text: ()=>JSON.stringify(fixtures.dummyRestaurants)})
    })
    it('should call fetch api with correct parameters', ()=>{
        fetchRestaurants()
        expect(global.fetch).toBeCalledWith(RESTAURANTS_URL)
    })

    it("should return response on fetch success", async ()=>{
        const restaurants = await fetchRestaurants()
        expect(restaurants).toEqual(fixtures.dummyRestaurants)
    })

    it("should return null on fetch error", async ()=>{
        global.fetch.mockRejectedValue("some error occured")
        const restaurants = await fetchRestaurants()
        expect(restaurants).toEqual([])
    })
})
```

### Some Explanations
 - ‘fetch’ is a global variable so we used “jest.spyOn” function to mock 
 - ‘fetch’ property of “global” object. “global” object is equal to “window” object in browser.
 - “mockResolvedValue” sets mimic value resolved by fetch by passing object with text function.
 - “mockRejectedValue” mimics the error case in fetch

### Steps to Make the Test Pass
 - Add “RESTAURANTS_URL” constant in “utils.js” file

``` javascript
export const RESTAURANTS_URL = "https://gist.githubusercontent.com/devbkhadka/39301d886bb01bca84832bac48f52cd3/raw/f7372da48797cf839a7b13e4a7697b3a64e50e34/restaurants.json"
```

fetchDistance function should look like below

``` javascript
export async function fetchRestaurants() {
    try{
        const resp = await fetch(RESTAURANTS_URL)
        const respStr = await resp.text()
        return JSON.parse(respStr)
    }
    catch(e) {
        console.log(e)
        return []
    }
}
```

### Some Explanations
 - We are getting the restaurants list for git raw url which returns text response. So we are using “text” property of “resp”.
 - We are parsing response string to javascript object

## Implement Calculate Distance
> Start here by checking-out “[4-fetch-restaurants](https://github.com/devbkhadka/jest-tutorial/tree/4-fetch-restaurants)” branch

### Test Cases for calculateDistance

```
calculateDistance
    - should return distance in meters from center to a location given in degree
```

Test code for calculateDistance should look like below. Add it at the bottom of utils.test.js file

``` javascript
describe('calculateDistance', ()=>{
it('should return distance in meters from center to a location given in degree', ()=>{
    const testLocationPairs = [
        [ 40.76404704,-73.98364954],
        [ 26.212754, 84.961525],
        [27.699363, 85.325500],
        [ -11.166805, 38.408597],
    ]
    const expectedDistances = [12109725, 168479, 1181, 6647488]
    const calculatedDistances = testLocationPairs.map((location)=>{
        return calculateDistance(location)
    })
    // Test calculated values with in 1km range of expected value
    expect(calculatedDistances.map(d=>Math.floor(d/100)))
        .toEqual(expectedDistances.map(d=>Math.floor(d/100)))
    })
})
```

### Steps to Make the Test Pass
 - Add constants below at top of utils.js file

``` javascript
export const CENTER_LOCATION = [27.690870, 85.332701]
const EARTH_RADIUS_KM = 63710
const PI_RADIAN_IN_DEGREE = 180
Add following code for calculating distance
export function calculateDistance(location){
    const [x1, y1] = convertCoordinateFromDegreeToRadian(location)
    const [x2, y2] = convertCoordinateFromDegreeToRadian(CENTER_LOCATION)
    const term1 = Math.sin((x2-x1)/2)**2
    const term2 = Math.sin((y2-y1)/2)**2 * Math.cos(x1) * Math.cos(x2)
    const distance = 2*EARTH_RADIUS_KM*Math.asin(Math.sqrt(term1+term2))
    return distance * 100
}
function convertCoordinateFromDegreeToRadian(point) {
    const [x, y] = point
    return [x*Math.PI/PI_RADIAN_IN_DEGREE, y*Math.PI/PI_RADIAN_IN_DEGREE]
}
```

> We are using [haversine distance formula](https://en.wikipedia.org/wiki/Haversine_formula) to calculate distance in earth’s surface

> **Excercise:** Validate the location parameter, Latitudes range from 0 to 90. Longitudes range from 0 to 180

**Hint:** verify that passing invalid value throws error using “expect(function).toThrow()”

<br>
Your tests should pass now. You can check in browser if it works or not by running “npm start”

<br>
I will appreciate any feedback, question and criticism. Your small encouragement means a lot, please don’t forget to clap like.


## References
 - [Understand some react concepts](https://reactjs.org/docs/hello-world.html)
 - [Common Jest Matchers](https://jestjs.io/docs/en/using-matchers)
 - [Jest Mocking](https://jestjs.io/docs/en/mock-functions)
 - [React Testing Library Cheatsheet](https://testing-library.com/docs/react-testing-library/cheatsheet)