Testing react apps with jest and enzyme.

While developing a piece of software, testing is an important aspect. It helps us to check whether while making some changes, we have not broken the code. When the app grows bigger, we have to make sure that we are not breaking it by making any change.

Let’s dive in and see what we can do with jest and enzyme together.

We are going to follow TDD ( Test Driven Development ) approach in this tutorial. i.e write the tests first (which will fail for the first time) and then write the code to pass the test.

To test react.js app, we can use Jest and Enzyme together.

Jest is a javascript testing framework which can be used to test any javascript code. Doesn’t matter its react, angular, babel, typescript. node.

Enzyme is Javascript testing framework to test the react code.

Let’s start making a smaller counter app , in which using two buttons. we can increase or decrease the counter. A very basic app.

The end product would look something like this.

First, let’s create a new project using create-react-app.

create-react-app counter-test-project

cd counter-test-project

And we have to install the dependencies enzyme & jest .

we need to install the enzyme with the adapter corresponding to the version of react we are using. So we will install enzyme-adapter-react-16.

create-react-app is having jest already inside. we can skip installing it.

npm i enzyme enzyme-adapter-react-16 –save-dev

Now we are ready with the dependencies installed.

By default, create-react-app have the jest installed, and it have a test in file App.test.js.

Jest automatically detects test files with suffix .test.js or .spec.js. It also detect the files kept in the folder __tests__.

To test the app, lets run the following command.

npm run test

and press ‘a’ to run all the tests.

You will see that the test is passed. In a block something like this.

So the default test is passed. Let’s change the content of App.test.js, App.js to the below code.

// App.test.js
import React from 'react';
import Enzyme, {Shallow} from 'enzyme';
import EnzymeAdapter from 'enzyme-adapter-react-16';

import App from './App';

Enzyme.configure({adapter : new EnzymeAdapter()});

test('renders without any error', () => {
});
// App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
    </div>
  );
}

export default App;

Let’s try to understand the above code.

So initially, we are configuring the Enzyme adapter with the Enzyme. This is done to make sure that we are providing the compatibility between the enzyme and the installed version of react i.e version 16.

test method is used to run the test. The first arguement is the test name. The second arugument is the function which we are testing.

So for now, we have kept the function blank. So there is nothing happening inside that function block. So if we run this test, it will be passed. The result would be again same. Test Passed.

Test if the components render

In the app, we have a few components which we have to test. Like as

Increase Button – to increase the count

Decrease Button – to decrease the count

Display – to display the count

The first part is that we have to check whether those individual components are rendering or not.

So to test that, lets add the below code to App.test.js.

// App.test.js
// .....
test('renders without any error', () => {
    const wrapper = shallow(<App />);
    const appComponent = wrapper.find("[test-class='app']");
    expect(appComponent.length).toBe(1);
});

shallow() – shallow function is used to test a component as a unit, and to ensure that your tests aren’t indirectly asserting on behavior of child components. It returns a wrapper around the rendered output.

wrapper.find( selector ) is used here to find the element using the selector. The selector can be any of these.

  • class syntax (.foo.foo-bar, etc.)
  • element tag name syntax (inputdivspan, etc.)
  • id syntax (#foo#foo-bar, etc.)
  • attribute syntax ([href="foo"][type="text"], and the other attribute selectors listed here.)
  • universal syntax (*)

We could have used class selector or id selector in our above code, but we have not. Because classes and ids can be changed based on the developer’s need. We needed something which wont’ be affected or changed later when we modify the app. That is the reason, we used attribute syntax here. our attribute here is [test-class='app']

Then , we are expecting the occurrence of appComponent to be 1.

Simple it is, right?

Big Brother Lol GIF by Big Brother After Dark - Find & Share on GIPHY

If we look at the test output, we will see that the test is failed.

Let’s modify the code by adding the attribute test-class=”app” to the div inside the app.js file.

// App.js
// ...
function App() {
  return (
    <div className="App" test-class="app"  >
      
    </div>
  );
}
// ...

And the test is passed.

Also we need to check whether the buttons as well as the display is rendering or not. Let’s add tests for those also. And modify App.js to pass the test. We will add display and the button to the App.

// App.test.js
// ...
test('renders without any error', () => {
    const wrapper = shallow(<App />);
    const appComponent = wrapper.find("[test-class='app']");
    expect(appComponent.length).toBe(1);
});

test('renders increament button without any error', () => {
    const wrapper = shallow(<App />);
    const incButtonComponent = wrapper.find("[test-class='incButton']");
    expect(incButtonComponent.length).toBe(1);
});

test('renders decreament button without any error', () => {
    const wrapper = shallow(<App />);
    const decButtonComponent = wrapper.find("[test-class='decButton']");
    expect(decButtonComponent.length).toBe(1);
});

test('renders display without any error', () => {
    const wrapper = shallow(<App />);
    const displayComponent = wrapper.find("[test-class='display']");
    expect(displayComponent.length).toBe(1);
});
// App.js
// ...


function App() {
  return (
    <div className="App" test-class="app"  >
      <h1>Count</h1> 
      <h2 test-class="display">3</h2>
      <button test-class="incButton"> Increase </button>
      <button test-class="decButton"> Decrease </button>
    </div>
  );
}

export default App;

Check the button functionality

shallow also allows us to interact with the state and props related to the component it is rendering. We can use the following methods.

.state([key] – returns the state of the component. If key is given, that particular value is returned.

.setState(nextState[, callback]) – sets the state of the component.

Now, using these above methods, we will write few more tests to check

  • if the counter starts from 0 or not.
  • if the buttons change the counter or not.

The process of this would be : –

  • get the wrapper for app component
  • set the counter in the state
  • find the button
  • simulate a click over that button
  • update the wrapper for app component (to force re-render)
  • check if the display is having a desired value

The code looks like below.

// App.test.js
// ...
test('counter starts at 0', () => {
    const wrapper = shallow(<App />);
    const initialState = wrapper.state('counter');
    expect(initialState).toBe(0);
})

test('increament button increaments the counter', () => {
    const wrapper = shallow(<App />);
    const counter = 2;
    // set the counter
    wrapper.setState({ counter });
    const incButtonComponent = wrapper.find("[test-class='incButton']");
    // simulate button click and check if counter updated or not
    incButtonComponent.simulate('click');
    wrapper.update();
    const displayComponent = wrapper.find("[test-class='display']");
    expect(displayComponent.text()).toBe((counter + 1).toString());
})

test('decreament button decreaments the counter', () => {
    const wrapper = shallow(<App />);
    const counter = 2;
    wrapper.setState({ counter });
    const decButtonComponent = wrapper.find("[test-class='decButton']");
    decButtonComponent.simulate('click');
    wrapper.update();
    const displayComponent = wrapper.find("[test-class='display']");
    expect(displayComponent.text()).toBe((counter - 1).toString());
})

To use state in the app component, let’s convert it into a class component

import React, {Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0
    }
  }
  render() {
    return (
      <div className="App" test-class="app"  >
        <h1>Count</h1> 
        <h2 test-class="display">{this.state.counter}</h2>
        <button 
          test-class="incButton"
          onClick = {() => this.setState({counter: this.state.counter + 1})}
          > Increase </button>
        <button 
        test-class="decButton"
        onClick = {() => this.setState({counter: this.state.counter - 1})}
        > Decrease </button>
      </div>
    );
  }
}

export default App;

Not all the test are passing. And we have successfully tested the implementation and functioning of these components.

What’s next?

This was just the introduction on how to use jest and enzyme with react. You can have a look at the docs for more references. And start implementing tests using these in your projects to get better at it.

Enzyme – https://airbnb.io/enzyme/

Jest – https://jestjs.io/docs/en/getting-started

Hope you like this blog! Keep visiting. I am gonna write some more good stuff for you. 🙂

To get a email notification for my next blogs. Please subscribe to the newsletter.

Leave a Reply

Your email address will not be published. Required fields are marked *