As I mentioned a few posts ago, we’ve been doing some front-end projects in React and Redux at work. We use test-driven development (TDD) so we’ve been figuring out the best way to test-drive our Redux applications.

I’ve written extensively about my approach to testing in the Getting Testy series. How do those principles and practices apply to a React/Redux application?

The Redux documentation has a fairly extensive section on writing tests which is a reasonable starting point. There’s also an open issue about improving the advice given in that section.

Background

Here’s some of the background information I wrote about Redux in my earlier post:

Redux is “a predictable state container for JavaScript apps.” Redux implements something like the Flux architecture using ideas from Elm. While not coupled to React, Redux is often used alongside it.

This post is also not a Redux tutorial; for that I highly recommend watching Dan Abramov’s video series on egghead.io. Even if you don’t think you’ll ever use Redux, it’s worth watching the videos to learn how to effectively teach a subject. The instructional flow of these videos is incredible.

The basic architecture of a Redux application consists of a single object containing all of the state of the application. The current state is held in a store. The UI is rendered as a pure function of the state, which makes React a great fit. The state is never modified directly; rather, user actions and/or async activities create actions which are dispatched through the store. The store feeds the current state and the action through a reducer which returns the new state of the application. The store replaces its state with the new state and updates the UI with it, completing the cycle.

Now What?

From this background, the main things we need to think about testing are Actions (usually built by Action Creators), Reducers, and Components. Components are often wrapped in Containers which are responsible for mapping a subset of the State into properties to be used by the Component and for mapping the Store’s dispatch function into callbacks to be used by the Component. We need to think about testing these Containers and their mapping functions as well.

While the documentation I referred to above gives code advice, it also suggests writing tests that I don’t usually write because I don’t find them valuable. I talk about tests I don’t write in Getting Testy: Why?, so I won’t belabor the points here.

How should we test React/Redux applications?

End to End Tests

I recommend writing a few end-to-end tests that check the main features of the entire application. These tests serve as “hookup” tests that make sure everything is put together correctly. These tests tend to be much slower than the unit tests, so only write as many of these as you really need. See Getting Testy: Layers for more on this.

I’m going to focus on the lower layers for the rest of this post.

Actions and Action Creators

Redux Actions are simple JavaScript objects. By convention, they have a type property that is a string describing the action. Any other properties in the object are up to the application developer. Action Creators are functions that create Actions. Here’s an example action creator adapted from the Redux documentation:

Example Action Creator
function setVisibilityFilter(filter) {
return {
type: 'SET_VISIBILITY_FILTER',
filter
}
}

In my opinion, most Action Creators aren’t worth testing in isolation. The tests for them are extremely simple and just repeat the code being tested.

The purpose of an Action Creator is to remove the duplication involved in creating an Action. If we want to dispatch the same Action from multiple parts of our application (including a Reducer test), we’d have to repeat the entire Action object structure everywhere. So, we extract the duplication into a function: the Action Creator.

I test the Action Creator indirectly by using it in a Reducer test (see below).

There are some exceptions to this rule:

  • If the Action Creator is doing some non-trivial work to build up the Action structure, like defaulting properties or computing properties based on its arguments, those things should be tested.

  • If you have adopted a standard action structure, such as Flux Standard Action, you might want to test that your Action Creators follow the standard. For Flux Standard Action, you could use the isFSA() function it provides to do this testing. I’d rather put this check in my top-level reducer to ensure that any action I dispatch has the correct format rather than testing each Action Creator independently.

  • If you’re using a middleware that accepts more complex Actions, you might want to test the Action Creators for those.

Reducers

Reducers implement the core logic of an application. They define how the state evolves in response to various actions. They’re often non-trivial. All of these factors make them very important to test.

Fortunately, the architecture of Redux makes them very easy to test, because Reducers are pure functions. Here’s an example, again adapted from the Redux documentation. I’m using Mocha and Chai.

Reducer Tests
import { expect } from 'chai'
import reducer from 'reducers/todos'
import ActionTypes from 'constants/ActionTypes'
describe('todos reducer', () => {
it('initializes the state', () => {
expect(reducer(undefined, {})).toEqual([])
})
it('adds a todo', () => {
expect(reducer([], addTodo('Run the tests'))).toEqual([
{
text: 'Run the tests',
completed: false,
id: 0
}
])
})
})

If the initial state or expected result get much more complex than this, I’ll extract it to a const.

Note that I’m using the addTodo action creator in these tests. I could use the basic Action object structure instead (as in the original example from the documentation), but that adds duplication, noise, and irrelevant detail to the tests. Also, as I mentioned above, this serves as an indirect test of the Action Creator.

Containers

Containers are a type of component identified by Dan Abramov in his article, Presentational and Container Components. They are usually generated with something like the connect() function from react-redux.

connect() takes two functions, mapStateToProps() and mapDispatchToProps(), each of which generate additional properties to pass along to the Component being wrapped by the Container.

As mentioned in the Redux documentation, it is possible to test a container by wrapping it in a Provider with a store created specifically for the test. Using shallow rendering, you could then ensure that the correct properties are passed to the wrapped Component.

To me, this feels like too much work. The main part of the Container definition is provided by connect(), which we can presume is well-tested. The part that we’re doing is isolated to the two mapping functions, so I’d rather focus my testing efforts there.

I’ll start with mapDispatchToProps(). In my experience, this function is usually trivial and not worth testing. In most cases, we’ll just use the shortcut provided by connect() and pass an object containing a number of Action Creators rather than a mapping function. Any test I’d write would just repeat the code I’m testing, making it not worth writing. If I need to write a more complex mapDispatchToProps(), I’d either export the function directly and test it, or extract a helper somewhere and test that instead. I haven’t yet written a mapDispatchToProps() function that I felt like I needed to test.

mapStateToProps() is a different story. It is sometimes simple enough to not require testing, but more often it does some non-trivial work. I recommend extracting behavior from mapStateToProps() and moving it into Selectors as outlined by Dan Abramov in this comment. A Selector is a function that selects part of the State. You can also write a Selector that returns data that is derived from the State; if you do much of this, you might want to look at a library like reselect which adds intelligent memoization for derived Selectors.

Selectors are much easier to test, since they are once again pure functions.

Selectors also provide a layer of encapsulation. The structure of the state is an implementation detail that should be hidden from the rest of the application. The only code that needs to know that structure are the Reducers and the Selectors. The rest of the application can be written in terms of the API provided by the Reducers (via the Action Creators) and Selectors.

Components

In a Redux application, most components are stateless and therefore very easy to test.

Components that have no internal logic are not really worth testing. Once again, the tests become almost identical to the code.

As soon as the component has internal state or needs any logic, it’s time to test it. There are a number of tools available for this purpose, and a number of approaches to take.

One of my main considerations when testing non-trivial components is that I don’t want to put irrelevant detail into the tests or assertions. Irrelevant detail clutters up the test, making it hard to understand what’s really being tested. As well, when the details change, a bunch of tests start breaking for no good reason, making them harder and more expensive to maintain.

Because of this, I’ve adopted two main techniques for testing components. When possible, I’ll use Artem Sapegin’s expect-react-shallow to test my components. This library allows me to test only the relevant parts of what is shallow-rendered by the component.

There are times, however, that even expect-react-shallow requires too much extra detail. When that happens, I’ll fall back to using the shallow rendering support from react-addons-test-utils.

For the latter, I usually create a little utility function to simplify the API:

shallowRender.js
import TestUtils from 'react-addons-test-utils'
export default function shallowRender(component) {
const renderer = TestUtils.createRenderer()
renderer.render(component)
return renderer.getRenderOutput()
}

For example, let’s say I want to test the Todo component adapted from the Redux documentation:

Todo Component
export default function Todo ({ onClick, completed, text }) {
const className = classnames(styles.todo, {
[styles.completed]: completed
})
return (
<li onClick={onClick} className={className}>
{text}
</li>
)
}

Note that I’m using CSS modules and the wonderful classnames library to handle the conditional logic of what classes to apply.

The only logic I’m interested in testing is whether or not the styles.completed class has been added to the li. Using expect-react-shallow, I can write the test this way:

Test with expect-react-shallow
describe('Todo', () => {
function todo({ completed }) {
return <Todo text='TODO' completed={completed} onClick={() => {}} />
}
it('styles a completed todo', () => {
expectReactShallow(todo({ completed: true })).to.have.rendered(
<li className=`${styles.todo} ${styles.completed}` />
)
})
it('styles an incomplete todo', () = {
expectReactShallow(todo({ completed: false })).to.have.rendered(
<li className=`${styles.todo}` />
)
})
})

This isn’t too bad. I wasn’t forced to include the onClick property or the text in my expected output. That’s a huge advantage of expect-react-shallow. But I was forced to include the irrelevant styles.todo in the className, which I’m not happy about.

In a case like this, I’ll fall back to using react-addons-test-utils:

Test with react-addons-test-utils
describe('Todo', () => {
function todo({ completed }) {
return <Todo text='TODO' completed={completed} onClick={() => {}} />
}
function className(component) {
return shallowRender(component).props.className
}
it('styles a completed todo', () => {
expect(className(todo({ completed: true }))).to.contain(styles.completed)
})
it('styles an incomplete todo', () = {
expect(className(todo({ completed: false }))).not.to.contain(styles.completed)
})
})

Note that I wrote a little helper function to extract the className property out of the shallow-rendered component.

With this approach, I can test exactly the part I care about and ignore the rest of the details.

Conclusion

This describes my current approach to testing React/Redux applications. What do you think?