Integration Testing in React

Whenever you Google “How to test a React application” you’ll find a plethora of blog posts and talks on unit testing and end-to-end testing with a browser, but there seems to be a dearth of information on how to write integration tests, i.e. the middle portion of the testing pyramid.

Integration tests serve a critical place in your testing plan by providing a balance between the speed of unit tests and the “real world” interactions of an end-to-end test.

How do you write integration tests for a React application then?

By definition, integration tests test the interactions between the various components of an application. For a React application, this means testing

  • interactions between React components, typically performed via calling prop functions such as <Component onClick={onClickHandler}>
  • manipulation of component state
  • direct manipulation of the DOM in React lifecycle methods

In order to test these kinds of interactions we need a way to not only render a whole component tree, but a means to render the component within a functioning DOM.

As of this writing, the only major libraries I can find which fit this bill are Airbnb’s Enzyme JS and react-testing-library. Enzyme is already an incredibly popular library for unit testing React applications whereas react-testing-library is an alternative developed by Kent C. Dodds which is specifically meant for integration testing.

Since I already use Enzyme heavily in all my unit tests, it’s tempting to just be lazy and stick with Enzyme, but I wanted to give react-testing-library a try and compare the two.

Enzyme

Enzyme is a testing library which gives you the ability to render React components in memory or to the DOM while providing a jQuery-like API for traversing the react component tree. Since Enzyme’s mount function renders your component to the DOM, there is nothing stopping you from rendering an entire React application. Unfortunately the same features that allow you to easily unit test React components: access to component props and state, encourages poor testing practices when writing integration component tests. A developer typically finds oneself writing code such as

// MyComponent.js
class MyComponent extends Component {
handleSubmit: ...,
    render() {
return (
<FormComponent onSubmit={this.handleSubmit}/>
)
}
}
// MyComponent.test.js
test('submitting form ads item', () => {
const wrapper = shallow(<MyComponent {...props} />);
    wrapper.find(FormComponent).props().onSubmit('New item');

expect(wrapper.instance().handleSubmit())
.toHaveBeenCalledWith('New Item');
});

For a unit test there is nothing wrong with the above test, but by calling the prop directly, we have decoupled FormComponent from MyComponent. It would be incredibly easy to accidentally break this code by forgetting to call onSubmit in our FormComponent code and the above test would not catch it!

You could say “Well, then don’t touch the props then!”, but if the library gives you the rope, you’re likely to hang yourself with it.

react-testing-library

Kent C. Dodds pointed out some of these flaws with using Enzyme as an integration testing tool and developed his own library of testing utilities on top of react-dom and react-dom/test-utils. His library react-testing-library attempts to solve these issues by providing an API which focuses on testing applications “as a user would”. This means an API that returns HTML Elements rather than React Components and querying functions which query by text content (i.e. the stuff that a user would actually see on a page) or HTML data attributes (in cases where fetching by text is not possible or practical).

Side by side example

To compare the two libraries, we can compare what an integration test would look like in Enzyme and react-testing-library on a simple TODO app. For the main root component we have

Followed by the TodoForm component for creating TODOs, the TodoItem component for displaying a TODO, and the TodoList component for displaying the list of TODOs

A very simple integration test we can write for this application would be

  • Entering a TODO in the TODO form and clicking “Add” should add the TODO to the end of the list of TODOs.

Let’s see what this test would look like in both Enzyme and react-testing-library

Enzyme Version

As we can see, it’s not too hard to write a simple integration test using Enzyme. However right away we can make two observations

  • The API revolves around manipulating ReactComponents via the ReactWrapper returned from find() or the React component itself using instance() rather than DOM nodes (what the end user would be interacting with).
  • There’s no built in way to wait for a DOM change to happen such as waiting on an AJAX call. In end-to-end frameworks such as WDIO or Cypress there is a “wait” command to wait for an element to appear. With Enzyme I had to roll my own crappy version.
This implementation isn’t meant to be the best. I’m sure there are better ones out there.

react-testing-library version

At its simplest, react-testing-library provides a simple API rendering react components and performing simple queries on the generated output. The basic functions the library gives you for querying are returned in the result of the render function, which include.

  • getByText: Fetches a node by the node’s text content
  • getByLabelText: Fetches a node by its corresponding HTML <label/> text.
  • getByAltText: Fetches an image node by its alt text
  • getByPlaceholderText: Fetches a text input by its placeholder text.
  • getByTextId: An “escape hatch” for fetching nodes by its data-testid attribute in cases where there is no text to fetch by.

Using this API we can re-write the earlier Enzyme test as

Right away I notice a couple of things that I like

  • API focuses on fetching by visible text on the page with getByText and getByPlaceholderText. This encourages you to test the application the way a user would see it rather than how your code would see it.
  • There’s a wait command just like in a Selenium test or in Cypressio.io, so I don’t have to use my crappy wait function.

So which one’s better?

After playing around a bit with these two libraries, I’ve come to really like react-testing-library and plan to give it a shot in my next project. I like its small API and emphasis on proper testing from a user’s perspective. If you’re not convinced that a new testing library is needed, Enzyme can still be used as an integration testing tool, as long as you are aware of its shortcomings.


Helpful links