Migrating from enzyme to testing-library/react

In December 2021 the developers for the very popular unit-testing library for React components enzyme announced they were discontinuing the development of the package. As mentioned in this article on dev.to, React 18 introduced too many breaking changes and developing a compatible enzyme version meant a huge, unfeasible rework of the package.

Since many geOps applications relied on enzyme for unit testing, its deprecation meant finding an alternative testing solution in order to be able to upgrade to React 18 in the future. Fortunately, there is an alternative library, which is also officially recommended by the React developers: testing-library/react.

First of all, it is important to specify a major difference between the two libraries. Apart from DOM testing methods, enzyme provides testing utilities accessing the component state, allowing component testing based on their internal APIs. Instead, testing-library/react focuses only on testing the actual DOM nodes. The developers argue that this approach is more user-centred, since the DOM is the final output the user actually interacts with. This approach also avoids having to adapt the test after structural changes with no influence on the component’s function and DOM output, which can be very time consuming.

A detailed documentation and migration guide describe how to rewrite enzyme tests with testing-library/react. testing-library/react provides a selection of query functions that can be used to select DOM elements just like the user would do it.

The following examples compare some tests in enzyme and rewritten in testing-library/react.

Snapshots

When checking HTML tree snapshots, the testing-library/react approach tests the native inner or outer HTML of the target element, better reflecting the actual DOM output.

enzyme

import { configure, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

test('should match snapshot', () => {
   const component = mount(<DialogComponent />);
   expect(component.html()).toMatchSnapshot();
 });

testing-library/react

import { render } from '@testing-library/react';

test('should match snapshot', () => {
   const { container } = render(<DialogComponent />);
   expect(container.innerHTML.toMatchSnapshot();
 });

Query selectors

react-testing-library provides a selection of query methods to find target DOM nodes. getByTestId is particularly useful, since it makes pinpointing target tags precise by querying their unique test id.

enzyme

import { configure, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

test('should close Dialog on close button click.', () => {
   const wrapper = mount(<ToggleButton />);
   const component = wrapper.find('ToggleButton');
   expect(component).toBeTruthy();
 });

testing-library/react

import { render } from '@testing-library/react';

test('should render toggle button', () => {
   const { getByTestId } = render(<ToggleButton />);
   const toggleBtn = getByTestId('toggle-btn');
   expect(toggleBtn).toBeTruthy();
 });

Events

Another convenient feature in testing-library/react is the straightforward component updating within tests using async - await. This makes it very easy to await changes after events such as clicks or input changes.

enzyme

import { configure, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

test("set state's text property on input change", () => {
   const text = 'Some text';
   const wrapper = mount(<TextInput value={text} />);
   expect(wrapper.find('TextInput').state().name)
     .toEqual(text);

   wrapper.find('textarea')
     .simulate('change', { target: { value: 'bar' } });

   expect(wrapper.find('TextInput').state().name)
     .toEqual('bar');
 });

testing-library/react

import { render, screen, fireEvent } from '@testing-library/react';

test("set state's text property on input change", async () => {
   const text = 'Some text';
   render(<TextInput value={text} />);
   const textarea = screen.getByTestId('text-area')
       .querySelector('textarea');
   expect(textarea.value).toBe(text);

   await fireEvent.change(textarea, { target: { value: 'bar' } });
   expect(textarea.value).toBe('bar');
});
written by Daniel Marsh-Hunn | 7/4/2022
More on this topic
7 min reading time › | Blog

Using Redis Subscriptions efficiently in Python

Inspired by the websockets broadcast feature we built a subscription multiplexer for redis subscriptions to subscribe to Redis channels and patterns once for all relevant clients.

read more
3 min reading time › | Blog

beyond tellerrand 2023

On 11th September 2023 members from the geOps frontend team set out to Berlin to attend a very interesting and extraordinary event: beyond tellerrand conference.

read more
3 min reading time › | Blog

React 18 support for create-react-web-component

We want to update five year old dependencies the trafimage-maps project. But it appears one project dependency is deprecated. What should we do? Fix the project or use something else? We decided to fix the project and give back to the community.

read more
8 min reading time › | Blog

Adding type hints to existing code in Python

The Python interpreter handles types in a dynamic and flexible way without constraints on what type of object a variable is assigned to. Since Python 3.5 programmers have the option to add type annotations to their code. Here we how it's done.

read more
2 min reading time › | Blog

From backend to frontend: we are looking for reinforcement

We have two vacancies for Fullstack or Backend Developers for our offices in Freiburg and Olten. If you want to bring your passion to a strong team, then you should apply.

read more
2 min reading time › | Blog

mapset 2.0 with MUI

mapset 2.0 has been widely refactored using the open-source react component library MUI (Material-UI).

read more

Contact

geOps AG
Solothurnerstrasse 235
CH-4600 Olten

fon: +41 61 588 05 05
mail: info@geops.ch
geOps GmbH
Bismarckallee 10
D-79098 Freiburg im Breisgau

fon: +49 761 458 925 0
mail: info@geops.de
Imprint | Privacy | Terms of service