Reducer Snapshots for Rapid Development

Bryan Fillmer
2 min readSep 18, 2017
Photo by Sabri Tuzcu on Unsplash

Test Driven Design (TDD) is a great way to ensure that you are getting the expected behavior from any given piece of code.

When writing tests I follow Eric Elliott’s advice in TDD the Rite Way and favor equality tests as much as possible. A test written this way, with explicit expected results, is easier to reason about, reduces edge cases, and documents the codebase.

Redux reducers lend themselves well to this development pattern. As our reducers are pure functions we can simply define inputs and outputs and run our function. An example, written using Jest:

/* global describe, test, expect */import {action, initialState, reducer} from 'state'
describe('reducer', () => {
test('handles ACTION_TYPE correctly', () => {
const name = 'A Name' const expected = {
name,
description: 'Some data that should not change.'
}
const actual = reducer(initialState, action(name)) expect(actual).toEqual(expected)
})
})

Simple and readable, we know exactly what the reducer should return when running the action mapped to ourACTION_TYPE.

There is a downside to this, specially when iterating quickly through a prototype or MVP: writing out the full expected shape for any given part of our application state can take some time.

The example above has state that is rather trivial and thus the test is not particularly lengthy. When leveraging Redux in production, and in particular when attempting to keep our state normalized and as flat as possible, the opposite quickly becomes the case.

Jest Snapshots to the Rescue

Jest comes with a handy feature called Snapshot Testing. Essentially what snapshots do are create a JSON schema representation of whatever you pass in and do future comparisons against that representation.

Although commonly demonstrated as a clever way to test React components they also happen to be useful for testing reducers. First, the above test written with snapshots:

/* global describe, test, expect */import {action, initialState, reducer} from 'state'describe('reducer', () => {
test('handles ACTION_TYPE correctly', () => {
const actual = reducer(initialState, action('A Name')) expect(actual).toMatchSnapshot()
})
})

Given the brevity of our reducer this does not seem like much of a gain, at least in lines of code. Lines of code savings do start to accumulate over time, however the big gain here is in maintainability and speed of development.

Setting up the test in this manner lets us get back to creating our reducer quickly. Tabbing over to the terminal, where we naturally have Jest running in watch-mode, allows us to watch the snapshot changes as we iterate on the logic contained within reducer.

Once the state shown in the snapshot reflects what we expect to see we simply update the snapshot and now have a baseline from which to compare any future changes.

A more complete example of using this pattern can be found at todo-and-tonic/todos.spec.js.

--

--