Testing React/Redux Apps with Jest and Enzyme
My team has been hard at work for the last few months rewriting and modernizing a key area of our product; we made the leap from a growing hodgepodge featuring Backbone.js, Bootstrap, and RequireJS to a modern React/Redux application leveraging webpack, Material-UI, and redux-orm.
It’s been a pretty steep (but fun) learning curve and we’ve tackled it with a lot of grace, kindness, and an eagerness to hold the bar high. Along the way, I found myself asking and being asked quite often, “are there any examples for how I’d go about testing x?” This post will dive into the basics, some details, and some not-so-common cases.
But first, some quick background info about the tools we use and our setup:
- Jest, a JS testing framework with minimal config, a well documented API, and great support for snapshot tests
- Enzyme, a JS testing utility for React apps that lets you test, traverse, and manipulate a component’s output
- react-test-renderer, enzyme-to-json, redux-mock-store, mockdate, and a few other helpful utilities
- We use the suffix
.spec.js
when naming our test files; Jest picks them up automatically this way and running them is triggered by a simplenpm test
command (which callsjest -verbose
)
Testing A Method
Let’s start with the basics. I want to test a method. In my case, I’ve got this handy method that takes a name and a substring and returns a <div>
where all matches of that substring in the name are wrapped in <b>
tags. I’m planning to use it for an autocomplete search component and it’s a great candidate for a unit test!
My method lives in a file named helpers.js
, so for the sake of organization and locality, I make a file in the same spot called helpers.spec.js
.
My assertions take on the form of a simple expect(x).toEqual(y)
, though if that isn’t what you’re looking for, Jest supports about a kazillion more matchers.
Testing A Reducer
Reducers are easy to test because they’re really just pure functions; they take the previous state and an action and return a new state. Here I’ve got an example of a reducer for a data table with filterable columns. The reducer manages the state of those filters, stored in a filters
array where each item is an object with an id
and a value
.
There are two chief actions I’d like to test for: UPDATE_FILTER
, which updates an individual filter’s value, and UPDATE_FILTERS
which does a bulk update of all the filters, overwriting all their values.
Testing this reducer is easier than you’d imagine. Like in our previous example, my assertions will also use the toEqual
matcher.
Note: when testing a reducer, remember to test cases where the previous state isn’t the default state. It’s easy to forget but so crucial.
Testing A Selector
Selectors follow a simple signature; given the store, they return a portion or slice of it. Take the following selector for example:
export const getUserName = state => state.user.name;
Writing a test for it is simple; it’s exactly like writing a test for any ordinary method:
Sometimes our selectors are more complex; they can be composed of other selectors, using redux reselect’s createSelector
method.
For example, suppose we had a to do list app with a selector, getOverdueTasks
, that returns tasks that are past their due date. My tasks have types; they can be personal
or work
related; and I’d now like to write a selector that only returns personal
tasks.
Testing this is just as simple as the previous example. I construct a state with both personal and work tasks and test that only overdue personal tasks are returned.
Components: Testing A Life Cycle Method
When testing React components, we often want to test the behaviour of life cycle methods (the special methods that are part of the component class that execute when a component is mounted or unmounted).
In this example, I’ve got a UsersTable
component that makes a request to fetch the users to display in its componentDidMount
implementation. We want to test that this does in fact happen since it’s crucial to the component rendering correctly.
We’ll start by creating an instance of the component using enzyme; note that since we are testing functional behaviour not presentational aspects, we’ll use enzyme’s shallow
method instead of mount
. This’ll avoid rendering any child components.
Since this component is connected to the store, we’ll need to wrap it in a Provider
or create a mock store to pass in to it. redux-mock-store makes this easy to do.
Also, we aren’t interested in testing the request to fetch users itself, just that it was called correctly. So we’ll use jest to spy on the component method that makes the request and assert with the matcher toHaveBeenCalled
.
Here’s our test:
Snapshot Tests
A snapshot is the DOM tree that represents a component. A snapshot test involves taking a new snapshot of a component and comparing it to the stored snapshot.
Snapshot tests are a relatively easy and low-cost way to ensure that your UI doesn’t change in an unanticipated way. Writing a snapshot test simple involves creating a *spec.js
file and running npm test
. A snapshot of that component is then recorded and stored in a __snapshots__
folder, created alongside your test file. A subsequent npm test
command will compare a new snapshot to the snapshot stored in __snapshots__
.
If changes you make to a component cause a snapshot test to fail, but you’re confident these changes are intentional; you can run npm test -u
to update the stored snapshot.
Snapshot Tests: A Presentational Component
Presentational components are only concerned with how things look; they don’t depend on redux actions or the store and they make excellent candidates for snapshot tests.
A great example is a pretty progress bar component. All it needs is the percentage of completed work. I want to safeguard against future changes that could accidentally affect how this component looks. Using react-test-renderer
, I can write the following snapshot test.
Snapshot Tests: A Connected Component
Warning: Are you sure you need to do this?
It’s often a good idea to separate container components (with state logic and flux actions) from presentational components (purely concerned with what things look like). This makes snapshot tests for container components unnecessary if their presentational components are well tested.
We can use redux-mock-store and enzyme to do this easily however. Here’s an example for the Users Table we saw earlier:
Snapshot Tests: Mocking Time
Sometimes we need to create snapshot tests for components that rely on today’s date, i.e. use new Date()
or perhaps another date utility. A date picker is an excellent example; today’s date is often highlighted with a different colour or style.
This means that if we took a snapshot of a date picker component, it would pass today and fail tomorrow (when today changes). We can get around this by mocking today’s date. There are many ways to do this, but I like mockdate.
So before we run our test, we’ll want to set today’s date to something fixed. And we’ll want to clean up when our tests are finished.
Here’s an example:
Snapshot Tests: Portals & Dialogs
Testing portals and dialogs can sometimes be tricky since they often render into a DOM node that is outside the parent component. react-test-renderer has limited support for portals, so I like to use enzyme and enzyme-to-json, which provides a snapshot serializer that works really well with portals.
Here’s a quick example:
You can also update your package.json
to add enzyme-to-json
as a jest snapshot serializer; this means you won’t need to import it and call addSnapshotSerializer
each time.
"jest": {
"snapshotSerializers": ["enzyme-to-json/serializer"]
}
That’s all for now — I hope this helps you get started with Jest and Enzyme. Happy testing!