Testing React components with Enzyme and Jest

by: Artur Dziedziczak

November 15, 2020

Introduction

Here I am again with some new post about React unit testing. I spend some time reading about Enzyme and at some point I thought it’s good to make a post about it. In this article I’ll lead you through different types of Enzyme rendering, differences between Enzyme and Jest, some examples of how to test more complex components which use asynchonous calls

Hope you will like it!

Enzyme

Enzyme [1] as you can read in their documentation is a:

As I first read about I though it works more or less like puppeteer [11] or Selenium WebDriver [2] environment which allows to simulate whole web browser to perform tests. Such tests are usually hard to write and maintain since they are not connected to codebase of application and you don’t use them to test single components

Enzyme gladly works differently but with its huge amount of features allows you to perform almost same test as you would do while simulating whole browser

Jest

Jest as it’s claimed on their main webiste is a

It allows you to run test suites and supports many different frameworks like. I haven’t really test if it works good with them but when you read how Jest creates snapshots of HTML it’s pretty convincing that you can use it for multiple frameworks

In this article I won’t focus on how Jest makes usage of Components snapshots but if you are curious this part of their docs describe whole process and it’s more or less 1 min read

Enzyme vs Jest

Ok some text was written about Enzyme and Jest but what’s the real difference between them? From documentation of both it’s not really obvious except that Jest can also work with different frameworks that React. That’s why I checked other articles including this one [12] which I highly recomend to read

So differences between Enzyme and Jest:

Table 1. Jest vs Enzyme - comparison table
JestEnzyme
Fully-featured testing framework which allows to test JS code and it’s not realated to any library like React,Angular,Vue.Works only with React components.
Compare React components by taking snapshots of HTML code that you need to maintain.Works with API which analyze React components which means no HTML to maintain.
Can work without Enzyme.Cannot work without Jest or other library that would start tests and make assersions

Shallow Rendering API [3]

It’s a type of rendering provided by Enzyme which allows only to render component that will be passed to shallow function. Not only it’s faster than Full Rendering but it also does not render child components which can help you reduce the scope of unit test

Full Rendering API [4]

At first sight it’s not really different from Shallow Rendering as API to query elements is similar. The main difference is that Full Rendering renders whole website. That means all child components of the component you pass to mount function will be rendered

It’s slower than Shallow Rendering but it allows to write much complex tests. In some cases you can even create full integration test of the app. Such test will be usually hard to maintain but it can fully test functionality with proper rendering of elements and order of function calls

Static Rendering API [5]

It allows to generate HTML from React Components and then query HTML elements via Enzyme API. It might be useful when you want to compare HTML of components and you are not using Jest for snapshots

Setup with react-create-app

All setup for my examples is based on react-create-app [13] which I highly recommend you to start with. Setting up React Typescript app with some test framework is time consuming and if you want to play around React react-create-app should fully satisfy your needs

Ok let’s start. To make your first React application which will support Enzyme framework first you need to create it. Depending on what Node tooling you have installed you can use npx,npm or yarn. In my examples I’ll use npx but if you want to use something different check official react-create-app documentation

Listing 1. Create Typescript React application in current directory
npx create-react-app --template typescript .
Listing 2. Adds enzyme and proper React adapter to project
npm i --save-dev enzyme @types/enzyme enzyme-adapter-react-16 @types/enzyme-adapter-react-16

As at the time of writing this React version is set to 16 you need to use @types/enzyme-adapter-react-16. More about different types of adapters and official Enzyme installation guide can be found here

Listing 3. Append Enzyme framework configuration at the end of src/setupTests.ts file
echo "import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({ adapter: new Adapter() });" >> src/setupTests.ts

And that’s it! From now on you should be able to work with Enzyme Typescript environment!

Examples

Ok, get ready for some Enzyme testing as shit is about to get real from now on. I prepared 3 examples which will show you how to use Enzyme and make usage of Shallow Rendering and Full Rendering. Hope you find it interesting and will come back to these examples in case of troubles with Enzyme

Example 1 - shallow rendering

To start I created simple React Component. Functional/Class Component it does not really matter

Listing 4. SimpleButton Component which functionality will be covered with Enzyme tests
import React from 'react';

export interface SimpleButtonProps {
    name: string;
}

export function SimpleButton(props: SimpleButtonProps) {
    return (
        <>
            
        
    );
}
Listing 5. SimpleButton.test.tsx file which covers basic test for SimpleButton Component
import React from 'react';
import { SimpleButton } from './SimpleButton';
import { shallow } from 'enzyme';

test('Test if button contain name from props', () => {
    const enzymeRenderedComponent = shallow();
    const button = enzymeRenderedComponent.find('button');
    expect(button.contains('test-name')).toBeTruthy();
});

So in first file we create simple React Component SimpleButton which renders button element with props.name. What I would like to test here if is when I pass name to this component it should render button which contains text passed from props

Let’s move to something slightly bigger

Example 2 - different renderings

In this example I’ll show you the difference between Shallow and Full rendering and when to use them

For that we will need two components. First one is SimpleButton which renders button element with some text. The second one ComplexComponent should render p tag with text and previously created SimpleButton

Listing 6. SimpleButton
import React from 'react';

export interface SimpleButtonProps {
    countryName: string;
}

export function SimpleButton(props: SimpleButtonProps) {
    return (
        <>
            
        
    );
}
Listing 7. ComplexButton
import React from 'react';
import { SimpleButton } from './SimpleButton';

export interface SimpleButtonProps {
    countryName: string;
}

export function ComplexButton(props: SimpleButtonProps) {
    return (
        <>
            

Another COVID lockdown is happening in: {props.countryName}

); }
Listing 8. ComplexButton.test
import React from 'react';
import { ComplexButton } from './ComplexButton';
import { mount, shallow } from 'enzyme';
import { SimpleButton } from './SimpleButton';

test('Dummy test to display different components', () => {
    const enzymeFullRenderedComponent = mount();
    const enzymeShallowRenderedComponent = shallow();

    console.log(enzymeFullRenderedComponent.debug());
    console.log(enzymeShallowRenderedComponent.debug());
});

test('Test if Complex button has proper texts', () => {
    const enzymeFullRenderedComponent = mount();

    enzymeFullRenderedComponent.contains('Another COVID lockdown is happening in: Poland');
    enzymeFullRenderedComponent.contains('Poland ! Welcome, first time huh?');
});

test('Test if ComplexButton has SimpleButton inside', () => {
    const enzymeFullRenderedComponent = mount();
    expect(enzymeFullRenderedComponent.containsMatchingElement()).toBeTruthy();
});

Let’s focus on the tests

First test is created to show you what is really rendered by Enzyme when you use Shallow and Full rendering. As you can see in the output of tests for mount (Full Rendering) and shallow render different things

The main difference is that mount renders whole ComplexComponent and shallow only component passed to it without child components

Listing 9. ComplexButton.test output
 PASS  src/components/buttons/ComplexButton.test.tsx
  ✓ Dummy test to display different components (48ms)
  ✓ Test if Complex button has proper texts (3ms)
  ✓ Test if ComplexButton has SimpleButton inside (3ms)

  console.log src/components/buttons/ComplexButton.test.tsx:10
    
      

Another COVID lockdown is happening in: Poland

console.log src/components/buttons/ComplexButton.test.tsx:11

Another COVID lockdown is happening in: Poland

Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total Snapshots: 0 total Time: 2.772s

In this simple example I showed you that when you use mount function the component that you want to test can be tested even when it has child components

Example 3 - async actions

Full Rendering with asynchronous code mock

Let’s get into some real life example. Usually when you create some components that send data they work on promises which take some time to fulfill. And here is the first big disadvantage of Enzyme that I couldn’t bypass with something that does not look like a dirty hack

The problem is that whenever you make some async behavior inside component and trigger it on shadow/mount Enzyme component the invoked function callback is not resolved immediatly after it is called

More about this weird behavior you can find here on Enzyme github but because it’s really messy and and solutions created in the comment of this issue are mostly horrible please check this blog [6] for more information

To conclude. As we want to make our test wait till Promise is resolved we need to have some utility function that will wait till all promises are resolved. This is the only semi-clean way I found and people usually suggest to follow this approach

Listing 10. test-utils.ts
export const flushPromises = async (): Promise => {
    return new Promise(
        (resolve: () => void): NodeJS.Immediate => setImmediate(resolve),
    );
};

What this pice of code does it creates a new Promise which is executed in the next iteration of the event loop. [7]. What this allows us to do is to wait till all promises are resolved. Whenever we call this function after asynchronous piece of code we will make sure that everything after await flushPromises() will be invoked when all promisses are resolved

Now to our tests

Listing 11. patients-api.ts
export async function fetchPatients(): Promise {
    const patients = await fetch('https://duckduckgo.com/?q=covid+patients&atb=v170-1&ia=web');
    const patientsJson = await patients.json();

    if (!Array.isArray(patientsJson) || Array.isArray(patientsJson) && patientsJson.some((element) => typeof element !== 'string')) {
        throw Error('Api responded with wrong data.');
    }
    return patientsJson;
}
Listing 12. CovidDownloader.tsx
import React, { useState } from 'react';
import { CovidList } from './CovidList';
import { fetchPatients } from '../../api/patients-api';

export function CovidDownloader() {
    const [patients, setPatients] = useState([]);
    const handleFetchPatients = async () => {
        const patients = await fetchPatients();
        setPatients(patients);
    }
    return (
        <>
            
            
        
    );
}

CovidList.tsx

import React from 'react';

export interface CovidListProps {
    patients: string[];
}

export function CovidList(props: CovidListProps) {
    return (
        
    {props.patients.map((patient) =>
  • {patient}
  • )}
); }

I created some API for fetching list of patients and CovidDownloader which uses this API and then display CovidList of patinents

The tests I prepared first check in shallow render if component itself renders without data. Then next tests checks if when interation with component is made the list is being loaded from API

Listing 13. CovidDownloader.test.tsx
import React from 'react';
import { shallow, mount } from 'enzyme';
import { CovidDownloader } from './CovidDownloader';
import { CovidList } from './CovidList';
import fetchMock from 'fetch-mock';
import { flushPromises } from '../../test-utils';
import { act } from 'react-dom/test-utils';

test('Test is CovidDownloader displays empty patients list at the beginning', () => {
    const component = shallow()
    component.containsMatchingElement();
});

describe('Testing e2e CovidDownloader actions', (): void => {
    afterEach((): void => {
        fetchMock.restore();
    });
    test('Test if user can download patients', async () => {
        await act(async (): Promise => {
            fetchMock.getOnce('https://duckduckgo.com/?q=covid+patients&atb=v170-1&ia=web',
                ['Artur Dziedziczak', 'Donald Trump', 'Ozzy Ozbrun']
            );
            const component = shallow()
            component.find('button').simulate('click');
            await flushPromises();

            component.containsMatchingElement();
        });
    })

    test('Test if patients are displayed in component', async () => {
        await act(async (): Promise => {
            fetchMock.getOnce('https://duckduckgo.com/?q=covid+patients&atb=v170-1&ia=web',
                ['Artur Dziedziczak', 'Donald Trump', 'Ozzy Ozbrun']
            );
            const component = mount()
            component.find('button').simulate('click');
            await flushPromises();
            component.update();

            const covidListComponent = component.find('CovidList');
            expect(covidListComponent.contains('Artur Dziedziczak')).toBeTruthy();
            expect(covidListComponent.contains('Donald Trump')).toBeTruthy();
        });
    })
});

Conclusion

I think these are real basics which you need to know to make tests with Jest and Enzyme. When I was learning all of this I also found some interesting notes that I would like to share with you

  • People complain about Enzyme a lot [6] no only because it has this problem with asynchronous testing but also because it is more focused on testing React Components than their behavior. A lot of people mention that nowdays you if you can choose framework for UI testing you should go with React Testing Library [8]. I haven’t try it but a lot of people mention it as a better solution so maybe it’s worth checking before going into full Enzyme setup.
  • You probably heard about Mocha [9] framework. It alows you to write test suites similar to Jest but when I tried to set it up with Enzyme it came out that it is not that easy and some config is required. It all comes down to jsdom [14] which is delivered with Jest package and not witk Mocha. So if you want to start using Enzyme without problems I would go with Jest if not this blog post [10] was very helpful when I was working with Mocha + Enzyme

And that’s it! Hope you found this article interesting

All examples can be found on my github

Sources

[1] “Introduction ⋅ Enzyme.” https://enzymejs.github.io/enzyme/

[2] “Selenium WebDriver.” https://www.selenium.dev/downloads/

[3] “EnzymeShallow,” GitHub. https://github.com/enzymejs/enzyme/blob/9a092dd4cc7c59dcfaa7476a5b204e58e0c49f7e/docs/api/shallow.md

[4] “EnzymeFullRendering,” GitHub. https://github.com/enzymejs/enzyme/blob/9a092dd4cc7c59dcfaa7476a5b204e58e0c49f7e/docs/api/mount.md

[5] “EnzymeStatic,” GitHub. https://github.com/enzymejs/enzyme/blob/9a092dd4cc7c59dcfaa7476a5b204e58e0c49f7e/docs/api/render.md

[6] “Asynchronous Testing with Enzyme & React in Jest,” Ben Ilegbodu. https://www.benmvp.com/blog/asynchronous-testing-with-enzyme-react-jest/

[7] “Understanding setImmediate(),” Understanding setImmediate(). https://nodejs.dev/

[8] “React Testing Library ⋅ Testing Library.” https://testing-library.com/

[9] “Mocha - the Fun, Simple, Flexible JavaScript Test Framework.” https://mochajs.org/

[10] “Testing React Components with Enzyme and Mocha,” Semaphore. https://semaphoreci.com/community/tutorials/testing-react-components-with-enzyme-and-mocha, Mar. 2016

[11] “Puppeteer/Puppeteer.” https://github.com/puppeteer/puppeteer, Sep. 2020

[12] “What Is the Difference Between Jest and Enzyme?,” Fast authoring of AI-stabilized end-to-end tests—codeless, coded, or both. https://www.testim.io/blog/what-is-the-difference-between-jest-and-enzyme/, Jan. 2020

[13] “Facebook/Create-React-App.” https://github.com/facebook/create-react-app, Sep. 2020

[14] “Jsdom/Jsdom.” https://github.com/jsdom/jsdom, Nov. 2020