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 fromfind()
or the React component itself usinginstance()
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.
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 contentgetByLabelText
: Fetches a node by its corresponding HTML<label/>
text.getByAltText
: Fetches an image node by its alt textgetByPlaceholderText
: Fetches a text input by its placeholder text.getByTextId
: An “escape hatch” for fetching nodes by itsdata-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
andgetByPlaceholderText
. 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
- In “Introducing the react-testing-library” Kent C. Dodds talks about the philosophy of why he created react-testing-library
- “React integration tests with Enzyme” is a great post from Pivotal which talks about their experience with using Enzyme as an integration testing tool.
- Github link for react-testing-libray https://github.com/kentcdodds/react-testing-library