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

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