Testing React applications using Enzyme and Jest manual mocks

Koszkota
12 min readNov 21, 2018

--

In the old dark times, people used to say that JavaScript is an awful language to write and very hard to test. Nowadays there is still a group of developers who think that JS is an evil virus of Satan, but there are fewer people who claim that JS is hard to test. That’s thanks to Jest and Enzyme, two awesome libraries that make testing a fun experience. In this note, I’ll show you how to combine the forces of Jest and Enzyme to smoothly test a React app that makes a call to an external API.

What is Jest?

Jest is a JavaScript unit testing framework. It can be used to test both vanilla JS and React. Jest provides everything you need for unit testing: test runner, assertions library, and mocking library. You can successfully use Jest to test simple assertions like this one:

describe(‘A very basic test’, () => {
it(‘is able to count’, () => {
expect(2+2).toEqual(4)
})
})

and complex operations that require mocking; you’ll see some examples of these later on.

Jest can run without Enzyme — Enzyme is just an add-on that can be used with Jest.

What is Enzyme?

Enzyme is a library that can be used to test code written in React. Unlike Jest, it has no application for vanilla JS — its functionality was designed for React only. Also unlike Jest, Enzyme is not self-sufficient — it needs to be used with a test runner and an assertions library as it doesn’t provide them itself. According to Enzyme docs, Enzyme doesn’t have a preference as to which testing framework you should use and it should be compatible with all major ones (eg. Jest, Mocha/Chai, Jasmine). Enzyme syntax is pleasant and highly intuitive as it’s mimicking jQuery’s API for DOM manipulation.

Using Enzyme to render React components in your tests

As you are all well aware, React structures the application into reusable “components”. The relation between the components is tree-like and can be illustrated like this:

The circle at the top is the root component, which has 2 child components and so on. Knowing this, we can move on and say that Enzyme can be used to render React components, manipulate them and make assertions on them.

Enzyme provides two main methods to render a component — shallow and mount.

Enzyme’s API has one more method — render — I’ll touch upon it briefly in the last paragraph of this post.

  • Shallow

shallow method renders the current component only one level deep and returns a shallow wrapper around it. What do I mean by saying “only one level deep?” I’m glad you asked. Enzyme’s shallow calls the render method only on the very component on which shallow was called. The first-level children of this component will be still “visible” in our tests — we will be able to count them and check some of their properties. However, children’s render methods won’t be called with shallow so we won’t be able to use whatever was rendered in them. The shallowed component is like an absent parent that knows it has children but doesn’t interact with them lol.

Shallow renders only the component for which it’s called

For this simple App component:

class App extends React.Component {
constructor() {
super();
this.state = {
contacts: []
}
}
}

We can create a ShallowWrapper in the following way:

const app = shallow(<App />);

shallow method is perfect for unit testing the logic of a single component that doesn’t need interactions with other components.

  • Mount

Unlike shallow, mount performs a full render — it creates the component for which it’s called together with the entire tree below the component. It’s ideal for use cases where you have components that may interact with other components.

Mount creates the component with its children

We create a ReactWrapper in a very similar way:

const app = mount(<App />);

I’ve mentioned above that shallow and mount create wrappers around the components: either an instance of ShallowWrapper, or ReactWrapper. Both wrapper types have predefined functions that can be used on them such as find, filter, contains, hasClass, or childAt. The functions make it nice and easy to test the components.

Everyone, meet the React app we’re going to test

The app we’re going to test is a simple contacts manager. It has an App root component which has a ContactForm child component — it renders a form that allows the users to add new contacts — and many Contact child components, each having three child components: DeleteContact, EditContact and SaveContact.

And this is how it looks when running:

Below I’ll explain how to properly test:

  • that the contacts are rendered in DOM.
  • that once we fill the form in ContactForm component and click submit, a new contact will be added to the React DOM.
  • App Component

App Component is the root component in which the call to the external API takes place. It happens immediately after the component is mounted (in componentDidMount), which is common practice. Once the contacts are back from the API, they are put into state from where they are rendered in contacts-list div (see the code in bold).

The code snippets that will be shown below are not enough to make the app work as they only reveal the lines relevant to the blogpost.

class App extends React.Component {
constructor() {
super();
this.state = {
contacts: []
}
}
componentDidMount() {
Api.getContacts().then(data =>
{ this.setState({ contacts: data}) })
}
render() {
return (
<div>
<h1> ... </h1>
<ContactForm
onAddContact={(contact) => ... }
/>
<div className="contacts-list">
{ this.state.contacts.map((contact, index) => {
return (
<div className="single-contact" key={index}>
<p1>{index + 1}.</p1>
<Contact
key={contact.id}
name={contact.name}
id={contact.id}
telephone={contact.telephone}
onDeleteContact={(c) => ... }
onEditContact={(editInformation) => ...}
/>
</div>
)
}) }
</div>
</div>
)
}
}

As you can see, the App component doesn’t call the API just by itself, but uses thegetContacts() method defined on the Api module to do it.

  • Api Module

A call to an external API has been extracted to a separate Api module what makes the code much easier to test as we can write a manual mock for this module; see more below. Also, it “dictates” DRY design as we can expect that when the future developers will be adding more API calls, they will do it in the Api module, by reusing parts of already existing logic.

This is what the Api looks like:

import axios from 'axios';class Api {static getContacts() {
return axios.get('/contacts')
.then((response) => {
return response.data })
}
static addContact(name, telephone) {
return axios.post('/contacts/add', {
"name": name,
"telephone": telephone,
})
.then((response) => { return response.data })
.catch((error) => { alert("Error in adding contact") })
}
}

getContacts() method makes a get request to /contacts API and once the promise is resolved, it returns received response's data.

addContact() method makes a post request to /contacts/add API and once the promise is resolved, it also returns received response's data.

Manual Mock of the Api Module

As mentioned above, we intend to test if the contacts are rendered in DOM. What makes testing it tricky is the fact that the contacts are fetched from an external resource. This external resource needs to be mocked.

Using a real API would deprive us from keeping control of what data is coming to our app during the test and can increase the testing time noticeably. To get a quick and consistent input data for our app, we need to simulate the call to the external resource and control what data is returned from it.

We can achieve it by using one of many open-source mocking solutions or pick one of four mocking techniques listed in the Jest documentation. The most popular ones are Automatic Mock — a technique where you create a mock to spy on calls to the class constructor and all of its methods, and Manual Mock where you write a new behaviour for the module.

From the techniques listed in Jest docs, I’ve picked Manual Mocks as they allow the tester to have full control over the implementation and make sure the Api mock can be reused in other tests as well.

Below is a manual mock of the Api module. It has the getContacts() and addContact() methods like the “real” Api module but instead of making a real API call, it has hardcoded return values. The hardcoded values resemble what is expected to be received from the real API call.

import axios from 'axios';class Api { static getContacts() {
return Promise.resolve([
{ name: "Bob", telephone: "11", id: 1 },
{ name: "Anna", telephone: "22", id: 2 }

])}
static addContact(name, telephone) {
return Promise.resolve([
{ name: "Weronika", telephone: "123", id: 3 }
])}
export default Api

As you can see, when calling the getContacts() method, the Api module — instead of making a real get call — returns a Promise which, when resolved, holds data about two contacts: Bob and Anna. addContact() method works in a similar way.

Now it’s time to place this mock in the file structure. On the same level, on which the module we’re mocking (in this case it’s Api) is located, we need to create a __mocks__ directory where we will place the mock module.

.
├── config
├── utils
│ ├── __mocks__
│ │ └── api.js // mock of real module
│ └── api.js // real module
├── node_modules
└── views

Testing time!

And now it’s time for the pay-off as we’re going to (finally) test our app!

The first test would be to check if the contacts appear in DOM when fetched from the API.

The first choice we need to make is what Enzyme method we want to use: shallow or mount?As both calling the API and rendering contacts takes place within the App component itself — in other words, we don’t need functionalities of any other component to fetch contacts and render them — shallowing will be the right choice.

Our setup will look like this:

import Adapter from 'enzyme-adapter-react-16';
import axios from 'axios';
import Enzyme, { shallow } from 'enzyme';
import React from 'react';
import Api from '../../Api';
import App from '../App.react';
jest.mock("../Api")Enzyme.configure({adapter: new Adapter()});describe('App', () => { const app = shallow(<App />);});

We use a manual mock of the Api module by calling jest.mock("../Api"). The Enzyme.configure({adapter: new Adapter()}) code is required to connect Enzyme with the React version we’re using which is React 16.

  • Testing state

Now we can write our first test. Just to check if the mock is working properly, we will write a test that checks the state of the app. This test is faaaaaar from ideal for two reasons. First of all, testing state, not behaviour, is a bad practice, as it can lead to mixing tests with implementation and will make the tests very sensitive to all changes in a code’s design. For example, if one day we decide to start using Redux instead of local state, such state test would become obsolete and more importantly, it would fail. Secondly, checking if the state holds the data from the API by hardcoding the API’s return value is essentially testing a situation that won’t ever happen in real life because the call to the real API is asynchronous, while our test runs in a synchronous ecosystem. Despite these important reasons, I believe that writing this test (and deleting it later!) is a good starting point to see if the mock is working.

it('has contact from Api call in state', () => {
expect(app.state().contacts).toEqual([{
name: "Bob",
telephone: "11",
id: 1
},
{
name: "Anna",
telephone: "22",
id: 2
}]);
});

In this test we expect that our app's state will contain the same contacts which the mock Api is returning in data. You may ask “BUT HOW!? We didn’t make the call to the API anywhere; all we did was shallowing the App component!” I’m right here with the answers, no worries. It’s all because the shallow method calls React lifecycle componentDidMount method, inside of which we’ve made the call to the API. Boom!

  • Testing behaviour

Enough with testing the state as it’s a bad practice anyway: it’s time for behaviour tests! In the test below, we no longer look for changes in the state, but instead we check if two children are placed in the contacts-list div after the app is mounted. We expect two contacts because this is the number of objects returned from out mock Api.

describe('app', () => {
it('has contacts in the conacts-list div', () => {
expect
(app.find('.contacts-list').children().length).toEqual(2);
})
})

To make sure that the children are actually contacts, we can check if they have the HTML class we expect:

expect(app.find('.contacts-list').children().filter(".single-contact").length).toEqual(2);

To be even more specific, we can check the exact text of the contacts in contacts-list by running the following test:

expect(app.find('.contacts-list').render().text()).toEqual(
"1. Bob 11 ✍ 💾 ❌ 2. Anna 22 ✍ 💾 ❌ "
);

In the code above, I’ve used another Enzyme method which is render(). This method is used to render React components to static HTML which allows us to easily examine the content of rendered element.

We have tested the first feature. The second feature that we want to test is adding contacts to the contacts-list div when the form in ContactsForm component is submitted. In order to test this, we will have to simulate filling in the form and submitting it.

Because the form we need to use is placed in the ContactsForm component (child of App component), shallow method will no longer be enough (as it doesn’t call render methods of children), and so we’ll have to use mount.

const app = mount(<App />);

First, we need to fill the form in the test. I recommend doing it in a beforeEach block as this setup can be later reused in other tests:

beforeEach(() => {
app.find('.input-name').hostNodes().simulate('change', { target: { value: "Weronika"}});
app.find('.input-phone').hostNodes().simulate('change', { target: { value: "123"}});
app.find('.btn-add').hostNodes().simulate('click');
});

In the code above, we:

  1. Target fields in the form using their classes (.input-name, .input-phone and .btn-add),
  2. Remove nodes that are not host nodes in order to keep only HTML nodes (hostNodes()),

“Hold on, what!?” Again, I’m glad you asked, cause the reason for using hostNodes() is actually pretty complicated. Let’s explain it step by step.

My form in the ContactForm component was created using FormGroup and FormControl components from the react-bootstrap library; see below.

import { Form, FormGroup, FormControl, ControlLabel, Button } from 'react-bootstrap';class ContactForm extends React.Component {
constructor() {
super();
this.state = {
name: "",
telephone: "",
}
}
render() {
return (
<div className="contact-form">
<form>
<FormGroup>
<ControlLabel>Name: </ControlLabel>
<FormControl
className='input-name'
onChange={(e) => ... }
/>
<ControlLabel>Phone: </ControlLabel>
<FormControl
className='input-phone'
onChange={(e) => ...}
/>
</FormGroup>
<Button className='btn-add' type="submit" onClick={}> Submit</Button>
</form>
</div>
)
}

Even though I used the class names .input-name, .input-phone and .btn-add in my code only once, there are in fact two nodes with this class: the HTML input field and a FormControl React component. If you don’t believe this, check the React dev tools screenshot for yourself:

React dev tools ❤

Because of this when we run app.find(.'input-name’), we’re going to find all nodes that have this class — both the FormControl component and the HTML input field.

In our tests we don’t want to deal with the FormControl component; we simply want to fill the HTML form. In order to do it, we either:

  • need to filter out the React node as I did in the code above app.find(‘.input-phone’).hostNodes()
  • target precisely the HTML input field only: app.find(‘input.input-phone’)

If we don’t do it, we’ll get the following error message:

Method “simulate” is only meant to be run on a single node. 2 found instead.

3. Simulate actions on nodes (either we change them or click on them).

Ok, as now we know what the beforeEach block is doing, it’s time to write a behaviour test!

First we need to call update() on the app node.

it('has one more contact on the website after adding', () => {
app.update()
})

Calling update() will cause the forceUpdate() function to be called. As a result, the mounted App component will be re-rendered and so we will be able to see the new contact in DOM! Read more about forceUpdate() in the React docs and about update() in Enzyme docs.

We can test if the number of children in the contacts-list div is now 3: two from the mock API and one added via the form.

it('has one more contact on the website after adding', () => {
app.update()
expect(app.find('.contacts-list').children().length).toEqual(3)

})

Finally, we can use render as we did before to check the text of the contacts-list div:

expect(app.find('.contacts-list').render().text()).toEqual("1. Justyna 11 ✍  💾   ❌ 2. Dominika 22 ✍  💾   ❌ 3. Weronika 123✍  💾   ❌ ");

Big thank you!

Thank you for reading this super long post! If you have any questions, send me a message!

--

--