Test-Driven Development: Unit Testing in React using Jest & Enzyme

“Test-driven development & continuous refactoring, two of the many excellent XP practices, have dramatically improved the way softwares are built”
 —— Joshua Kerievsky

Test-Driven Development (TDD) is a software development technique that requires unit tests to be written before the start of the development process. It plays an important role in the agile world as TDD is a basic practice of the Extreme programming (XP) method.

The primary goal in TDD is to write code that is clear and bug-free. We need to write tests for every small functionality of the application we are developing. TDD is a cyclic process that starts by writing the unit tests. This makes sure that all the basic requirements are covered and you have a clear picture of what you are trying to build before you write the actual code.

TDD Lifecycle

TDD has many benefits — one of the advantages of high test coverage is that it helps in easy code refactoring while keeping your code clean and functional.

Why is testing required?

It allows us to test major as well as minor components and features of the software. Well tested software provides allows us to detect errors during the early stages of software development, making the software safe and user friendly.

Unit Testing is testing individual units or components of an application, in order to validate that each of those units is working properly.
Jest is an open-source testing framework created by Facebook that makes it easy to perform unit testing in JavaScript.
Enzyme is React specific. It provides a bunch of helpful methods that enhances the way React components are tested.

Let’s understand the TDD approach by performing Unit Testing on our React application using Jest and Enzyme.

Once you setup your React app by using

npx create-react-app 

cd counter-app-test

Go tosrc/App.js. We will write a simple code to increment a counter whose initial state is 0, by 1 using a button with a increment function.
Modify the content to this:

src/App.js

import React, { Component } from 'react';
class App extends Component {
   constructor() {
     super();
       this.state = {
       count: 0,
     }
}
makeIncrementer = amount => () =>
      this.setState(prevState => ({
      count: prevState.count + amount,
}));
increment = this.makeIncrementer(1);
render() {
   return (
     <div>
       <h1>Count: {this.state.count}</h1>
       <button className="increment" onClick=                                          {this.increment}>Increment</button>
     </div>
     )
   }
}
export default App;

Our React app has some initial state count which is set to zero, and a button that, once clicked, increments this count state through the increment function which simply adds 1 to the value of count and updates the application state.

Setting up our environment for performing TDD with React:

Using the following versions for the tutorial:

{
"react": "^16.4.2",
"enzyme": "^3.6.0",
"enzyme-adapter-react-16": "^1.4.0",
"react-test-renderer": "^16.4.2"
}

Install Jest using Yarn:

yarn add --dev jest
A describe() function groups related tests together inside one test file. It takes a name parameter, which describes the component you’re testing, and a callback function where individual tests are defined with it.
What you want to test is wrapped in a call to the expect() function, before calling a “matcher” function on it. In the above example, toBe() is the matcher function used. It tests whether the value provided equals the value that the code within the expect() function produces.

We need to add a few packages to our react app to be able to test via Enzyme’s shallow renderer:

yarn add enzyme enzyme-adapter-react-16 --dev

Then, create a setupTests.js file within your src folder that lets Jest and Enzyme know about the Adapters used. create-react-app has been configured rightly, to run this file automatically, so that Enzyme is set up correctly before we move ahead with testing.

src/setupTests.js

import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });

Once the setup is done, we can begin writing tests for our application.

Enzyme provides shallow rendering to test our app’s initial state. A shallowrenders only one level of components deep and enables the simulation of user interaction.

Let’s move to src/App.test.js file and change its contents to look like this:

src/App.test.js
import React from 'react';
import { shallow } from 'enzyme';
import App from './App';

describe('App component', () => {
      it('starts with a count of 0', () => {
        const wrapper = shallow(<App />);
        const text = wrapper.find('h1').text();
        expect(text).toEqual('Count: 0');
    });


In the above test, the shallow render of our App is stored in the wrapper variable. Then, takes the text inside the h1 tag of the App.js and checks if the text is the same as we passed into the toEqual matcher function.

Run the test using yarn test on your terminal, you will see that the test passes.

Testing user interaction with react app

Some tests are written to check the behavior of the app when an event occurs. This could be used to test functions as well as how the UI elements would respond to the event.

Now, we will add tests to check if the value is incremented by 1 when the increment button is clicked. Add this snippet to the test file. You can keep or remove the previous test, would work fine in whichever way.

src/App.test.js

describe('App component', () => {
      it('increments counter by 1 when the increment button is clicked', () => {
         const wrapper = shallow(<App />);
         const incrementBtn = wrapper.find('button.increment');
         incrementBtn.simulate('click');
         const text = wrapper.find('h1').text();
         expect(text).toEqual('Count: 1');
});
});

The simulate() function on the incrementBtn variable simulates several DOM events on an element. We are simulating the click event on the button. Also, set up test, that the count should be equal to 1 now, after click event occurs.

Tests Passed

Now, let’s follow a different approach. We will add a test condition for the functionality that is not present in app.js , then we will run tests. Ofcourse, the test is bound to fail. We will write the required code as per the test condition and then make the test pass. This entire approach of letting the tests drive the code is known as Test-Driven Development (TDD)

Add another test within the describe() function:

src/App.test.js

describe('App component', () => {
      ...
it('decrements counter by 1 when the decrement button is clicked', () => {
        const wrapper = shallow(<App />);
        const decrementBtn = wrapper.find('button.decrement');
        decrementBtn.simulate('click');
        const text = wrapper.find('h1').text();
        expect(text).toEqual('Count: -1');
      });
    });

We can see the failed test result with the message. This is the way Enzyme describes the error. It means that the simulate() method was called for an element that has no related code yet.

Test-Driven approach

Once we add the associated code for the failed test condition, the test suite will be successfully executed.

src/App.js

import React, { Component } from 'react';
class App extends Component {
      constructor() {
        super();
        this.state = {
          count: 0,
        }
      }
makeIncrementer = amount => () =>
        this.setState(prevState => ({
          count: prevState.count + amount,
        }));
      increment = this.makeIncrementer(1);
      decrement = this.makeIncrementer(-1);
render() {
       return (
            <div>
               <h1>Count: {this.state.count}</h1>
                 <button className="increment" onClick= {this.increment}>Increment</button>
                 <button className="decrement" onClick={this.decrement}>Decrement</button>
            </div>
       )
     }
    }
export default App;

Now that we have added the decrement() function that decreases the value of count by 1, updates the counter state and also the decrement button for performing the event. All the tests will be passed successfully. Thus, we have implemented the TDD approach to build the decrement functionality in our app.


Snapshot testing helps you test that the output of your component is correct. Comparing it with the previous snapshot of your component, if both the tests match, then only your tests pass. This type of testing helps you avoid accidental changes to your components because Jest will always notify you if any difference is detected.

We can use Jest’s snapshot testing feature by installing an additional package react-test-renderer. Once this is done, you can import and write tests to perform snapshot testing. You can try out the TDD approach with snapshot testing as well. You can refer the code for the same on Github.

Conclusion

The variation of strictness for implementing TDD can depend on your use case. TDD approach can be widely used, as it boosts your productivity and improves the code quality, moreover applications developed using TDD techniques are more modular and flexible. When in an agile team, depending on your goal, you can decide where to implement the TDD approach.
You can find the example written in this tutorial on GitHub, you can check out, and run it locally. 😁

Shreya Ghate

Shreya Ghate