Choosing the right testing tool for your new React app

Soumya Sunny
Engineered @ Publicis Sapient
5 min readMar 5, 2021
testing functional components in react
Unit testing React functional components

Testing is the section that you always skip in the React documentation, don’t you? The same goes for your project also! Test cases are always a pain. But good test cases avoid regression issues and it gives you more confidence in your code. You should concentrate on covering the functionalities rather than merely reaching a high coverage.

Unit test cases test the component in isolation. Integration tests combine different components in the application and test as a group to ensure that they are working fine. In a UI application, this usually involves testing API calls or page navigation. End-to-end tests are for testing a particular user journey or flow, typically simulated in a browser environment (for example, sign up or checkout flow).

Popular unit testing libraries for React are Airbnb’s Enzyme and React-testing-library. The same can be used for integration tests also. Both Enzyme and RTL can be used with Jest ​or mocha, two popular test runners. Popular end-to-end tools are Puppeteer and Cypress.

For this blog, we will be concentrating on Enzyme and RTL. I started looking into React-testing-library when I hit a few roadblocks in my current project, which is using functional components and React hooks. The examples discussed here can be found in this github repository in respective branches. (Trust me, it is not yet another TODO app, but a basic Weather app).

Enzyme vs React-testing-library

Enzyme vs react-testing-library
Enzyme vs React-testing-library

Installation

RTL comes bundled with the create-react-app. You can start testing with zero-config. Create a test file and hit the command ‘npm test’. Done!

For Enzyme, you have to install Enzyme and an adapter depending on the version of the React you are using. For React v16, it is ‘enzyme-adapter-react-16’.

//setupTest.js
import { configure } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'

configure({ adapter: new Adapter() })

At the time of writing this, the adapter for React version 17 is not yet available. There are a couple of unofficial adapters though.

Concept

The primary guiding principle of RTL is “The more your tests resemble the way your software is used, the more confidence they can give you”. RTL tests our app from a user’s perspective whereas Enzyme can test the implementation details also. For example, if you want to test a user name text field using RTL, it can query by the label using the selector getByLabelText. But in Enzyme, you need to use id, CSS classes, the React component, or a combination of these.

The problem with the Enzyme’s approach is, when you modify your implementation, but not functionality, you still end up with a broken test. So let’s compare some codes. Testing a click event on our weather app’s ‘Get forecast’ button in RTL:

fireEvent.click(screen.getByText(“Get Forecast”));

Enzyme:

wrapper.find(Button).simulate(“click”);

The moment you add one more button to the component, Enzyme’s test fails. Even if you add another component instead of a button, with similar functionality, your test will break. This makes code refactoring a difficult process. Whereas the RTL test passes gracefully every time. Of course, you can use id and other CSS selectors in Enzyme. But compared to RTL selectors like placeholder, text, and role, they are more prone to changes. One more plus point I saw in RTL is, it has util methods that are focused on accessibility (ByRole and ByAltText).

Learning Curve

Enzyme has 3 types of rendering: shallow, mount and render. Shallow renders only one top-level component and it is used generally for snapshot testing. ‘Mount’ is used for component interactions. These many methods make things difficult for a newbie. But RTL has one and only one ‘render’.

Documentation

Enzyme has a vast documentation. When you want to know how to test the router or Redux connected components, you have to search the web. But RTL has everything under the same roof. This considerably reduces your time for writing the test cases.

Events

Simulating a dropdown selection is one of the most searched questions in stackoverflow, be it Material-UI, ant design, or React-select. Let’s see how both the frameworks handle events.

RTL: It has a fireEvent method that works in most of the scenarios. Create-react-app also installs a sibling package ‘user-event’. It simulates the events a real user would do while interacting with your application. For example, typing in a text field involves focus, mouseEnter, keyDown, keyUp…etc, and userEvent will execute all these events.

fireEvent.change(screen.getByTestId(‘select-city’), {
target: { value: ‘Bengaluru’ },
});

Enzyme: Two different methods exist depending on the type of render.

Mount:

wrapper.find(Select).props().onChange({ target: { value:‘Bengaluru’})

Shallow:

wrapper.find(Select).simulate ({ target: { value: ‘Bengaluru’ } })

React-select:

My question in stackoverflow about simulating the create event of a Creatable-select using Enzyme is not yet answered, as of 03/03/2021. If it was RTL, I wouldn’t have asked that question itself. RTL provides a sibling package ‘react-select-event’, which provides helper methods for events.

One more difference I noticed is, when a state change happens as a result of a simulated event, you get the below console error. For fixing it, you need to import the ‘act’ from test-utils package. However, this works seamlessly in RTL. There are no extra steps needed.

act-warning-enzyme

Snapshot testing

Snapshot testing is a very important part of visual regression. The output of Enzyme’s render methods is objects. So for effective snapshot testing, you need to set up a serializer called enzyme-to-json. But the rendered output of RTL is the dom itself. It doesn’t need any other configuration.

So what is up with functional components?

With the introduction of hooks, most of the methods in Enzyme have become obsolete. They are lagging in terms of features. In fact, Airbnb conceded that they use RTL for some of the projects.

But what are ReactTestUtils and TestRenderer?

ReactTestUtils makes it easy to test React components in the testing framework of your choice. TestRenderer package provides a React renderer that can be used to render React components to pure JavaScript objects, without depending on the DOM or a native mobile environment. Both Enzyme and RTL are built on top of these packages.

Conclusion

From the below points, it is evident that RTL has an upper hand. But if your project requires you to test the state or internals of a component, then go for Enzyme.

Enzyme vs React-testing-library

The Future?

The NPM trends graph shows a stark rise in RTL’s downloads from mid of 2019 and it overtook Enzyme in the mid of 2020. One year ago, Airbnb transferred Enzyme’s ownership to a community-led Enzymejs Github Organization. Let’s hope to see a tight competition.

npm trends
npm trends for enzyme and react-testing-library

Inspired to read more about React testing? Learn from the creator of RTL Kent C Dodds or this blog.

--

--

Engineered @ Publicis Sapient
Engineered @ Publicis Sapient

Published in Engineered @ Publicis Sapient

We fuse startup thinking and agile methods to help established companies increase value, drive efficiencies and thrive in an evolving world

Soumya Sunny
Soumya Sunny