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?
Here’s some of the background information I wrote about Redux in my earlier post:
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.
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
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:
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 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.
If the initial state or expected result get much more complex than this, I’ll extract it to a
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 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,
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.
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:
For example, let’s say I want to test the
Todo component adapted from the Redux documentation:
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:
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:
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.
This describes my current approach to testing React/Redux applications. What do you think?