How to Snapshot Test Everything in Your Redux App With Jest

Posted on 2016-12-07

Javascript fatigue is something I always try to fight. Some times it feels extra painful because I’m measuring it against the intuition I’ve built based on my experience with different platforms such as the JVM, .NET, Ruby, and Go. Put aside the ever-growing list of Javascript single page application frameworks, competing modules, and competing view libraries, we still have a considerable fatigue from the process of choosing a testing stack.

A testing stack covers running and rerunning tests reliably and quickly, integrating with coverage statistics, with tooling — by offering different reporters, mocking libraries, and test flavor (BDD is one), and in some occasions the level of integration tests — against real browsers, headless browsers, or a virtual DOM. Then, you can pick and choose from mocha, Jasmine, ava, Istanbul and nyc, and tape and more.

One beautiful way to alleviate this fatigue that I can identify from the endless stream of intriguing projects in the Javascript world today, is what Facebook has done with Jest.### Just Use Jest

If you remember jest from previous, older, encounters — you might have remembered a slow library that’s not as widely adopted. During 2016, Facebook has transformed Jest to be a state-of-the art testing library, and had the guts to point us to fixture tests as a first-class-citizen in our testing approach (read: snapshot testing, more on that later).

For those with bad Jest experience — forget about that experience. I feel that by now Jest is a different tool,but don’t take my word, pause to read about how Facebook tackled performance, revamped developer experience here, and here and here, and here. And so, with Jest, you get the feeling developer experience is important.

That Fixture Thing

From Jest 14, you could use snapshot testing. Snapshot testing is a fitting name for plain old fixture testing, it works by dumping test state into a format Jest can compare (JSON), and performs test validation against previous test results automatically, by comparing files results. If a result don’t exist yet, it will get created. It’s a take on things like vcr, but it works for everything. This article will show you how to use Jest for everything.

Before that, you should be aware that there was the fixture discussion once, in the Ruby world. In the center was David Heinemeier Hansson (aka DHH), creator of the Rails web framework and founder of Basecamp. As the story goes, the Rails and Ruby community moved out of fixture based tests because of runtime slowness and maintenance overhead, so much that they called fixture testing an anti-pattern. DHH stood his ground, and other people responded the same, it was a big deal and went on for a while, until years later — no one cared any more.

On a personal note, I think DHH was right, and people on the other side of the discussion were right in their own way, but for me, like DHH, I make fixtures take a large part in my testing strategy — and it doesn’t matter what technology I use; if a fixture test harness isn’t there, I’ll build it. With Jest snapshots, it comes built-in.

It’s possible that one day people will feel passionate and dispassionate about Jest snapshot testing, enough to resurface this discussion — but in the Javascript world rather than the Ruby world. At least now you have a field guide for this potential flamewar.

Snapshot Testing

The classic example for snapshot tests, from the Jest docs, goes like this:

There’s so much value in this: it will render the view using a new test-renderer, will dump it into an intermediate, diff-able format (JSON), and save a file (“snaps” it). Compared to left-hand-right-hand coding for assertions to validate a view, this is a complete win.

What makes snapshot testing so legitimate for this case is the fact that React and Redux took a more functional oriented approach. A good number of primitives around us are pure, for example pure components, and reducers, and there are more. In these cases we have input, process, and output. It’s perfect for fixture testing, and manually asserting the output instead, is a waste of time.

Let’s see how to test a Redux application, almost entirely with Jest.### Actions

Here’s how to test an asynchronous action that perform an HTTP request through a promise (using axios, mocked with moxios):

We use redux-mock-store which lets us assert on its internals, such as getActions(). The idea is to get to a JSON representation of the result of the action as fast as possible, and to bolt in place the store’s internal state in terms of the actions that it saw.

Testing a synchronous action is the a simplified variant of the async case, which will be along the lines of:

expect(store.dispatch(someAction())).toMatchSnapshot()

Views

Views are the poster-child of Jest snapshot tests. To snapshot test a view, you’ll need to use the react-test-renderer module:

$ yarn add --dev react-test-renderer

Here is a self-contained example that shows how to test a Foobar view:

Interaction

To test interactivity, I would say ditch snapshot testing, and go with enzyme. Don’t worry, you can still use Jest mocks to follow a “blessed” Jest stack. First, install enzyme and dependencies:

$ yarn add --dev enzyme react-dom

Note that for the moment you can’t use Jest snapshots and enzyme tests in the same physical file due to renderers being in conflict. You might want to separate the two kinds of tests in any case, because you’ll maintain the two kinds in a different manner — interaction tests often requires an understanding of the flow and the surrounding state, while snapshot tests are simple input-process-output, almost declarative tests.

Here’s how to test a component called Fuzzy that interacts on press in React Native:

With enzyme we find the first touchable and press it. But you could use what ever selector strategy you prefer.

Reducers

We’re back to functional land. This means testing will be easy; reducers are almost fun to test because they take a couple inputs, performs some processing, and return a predictable output. Sounds like another perfect case for snapshot tests (I use Immutable.js here for the state):

You might feel that this looks almost code-generated. And you‘d be right. There’s plenty of low-hanging fruit to grab here — how about just declaring an input, an action, and a subject? Let’s take a detour.

reducerTest(someState, ()=>someAction(), subjectReducer)
reducerTest(anotherState, ()=>someAction(), subjectReducer)
reducerTest(anotherState, ()=>anotherAction(), subjectReducer)

Which you can then compact into:

{
  [subjectReducer]:[
    [someState, ()=>someAction()],
    [anotherState, ()=>someAction()],
    [anotherState, ()=>anotherAction()],
  ],
  [anotherReducer]: [...]
}

And if we can get rid of the function there, by specifying their parameters out-of-band:

{
  [subjectReducer]:[
    [someState, someAction],
    [anotherState, someAction, param1, param2],
    [anotherState, anotherAction],
  ],
  [anotherReducer]: [...]
}

Given we have a registry of symbols, we can now turn this into a data file, where each string will be a lookup key into a live object (action, state, or reducer).

{
  "subjectReducer":[
    ["someState", "someAction"],
    ["anotherState", "someAction", 8, 15],
    ["anotherState", "anotherAction"],
  ],
  "anotherReducer": [...]
}

And of course, you can provide the values for the action (not using an action creator function) and state (not using a pre-made state variable).

Now you can have a proper test harness, without a single line of manual test set up.

import reducerTests from 'reducer-tests.json'
runTests(reducerTests)

This is a breeze through what’s possible. Here’s an open challenge — make a library out of this!

A Reducer Test Gotcha

When a reducer is a big ball of a black-box switch/case, you don’t know what actions trigger it and what actions don’t. It may be that when you cover all of the actions you know of in tests, one action slips through because, well, people. And since you’re only testing what you know — this can trigger bad behavior in production.

And so, we need to find a way to assert that a reducer does its job and responds to a closed set of actions, and these actions alone. You can do this with Flow using type annotations; but in either case I’m not happy about big switch cases because I think their lack of modularity doesn’t fit with what Redux is trying to do.

This is why I compose my reducer out of handlers using Duet:

export const reducerMap = Object.assign({},
  rehydrateHandler,
  statusActionHandler,
  requestActionHandler,
  saveUserHandler,
  deleteUserHandler,
)
export default createReducer(initialState, reducerMap)

Being that the map is exported, I can easily snapshot test it:

expect(reducerMap).toMatchSnapshot()

And it closes over the set of actions that the reducer responds to, and only to that set.

Connect and Selectors

We’ve tested the bits that make a Redux app. But we need to be careful not to neglect the bits that connect the bits — the Redux connect function, mapping functions, and selectors. I like to place and export the Redux connect logic and selectors in a separate file, and in the same time export the smart component it connects — without connecting it.

By now I’m guessing you’ve got the idea: to snapshot test, find a way to translate these into JSON. For the case of selectors — that’s easy. Its practically what they do — provide state and props to a function and it spits out another form of state. For a selector called getUsers this is it:

expect(getUsers(state, props)).toMatchSnapshot()

For the mapping functions in connect, testing may be tricky. You can assert that mapping is in place by generating a snapshot, which will capture the names of the functions because what ever mapping is doing, it’s generating a map. But to assert that a function called doFooBar dispatches doFooBarAction — you’ll need to invoke it. This is a good motivation to find a declarative way to take care of that bit — or leave it to end-to-end tests or integration snapshot tests — where you connect everything, interact, and snapshot everything.

Libraries

Finally, to test a library — if you can position it to spit out anything that you can convert to JSON, you can snapshot test it.

Finding the Right Balance

If you take this approach, you’ll have a code base that will be composed of:

  1. Testing with snapshots.
  2. Interaction tests.
  3. Sniping tests.

Being that a sniping test is a test that you’d only like to snipe a few values, properties, or make a few assertions. These would be much less rigid than snapshot tests (snapshots will break on any change).

You’ll have to balance (1) and (3) and shuffle around how you want to write your tests. Mostly I’d say — don’t be afraid to go with snapshot tests for everything, because replacing those with regular, sniping tests, will be easier than the other way around.> Hacker Noon is how hackers start their afternoons. We’re a part of the @AMI family. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.

If you enjoyed this story, we recommend reading our latest tech stories