Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A guide to writing high impact redux tests.

Some people will tell you to test everything in your app. If you have time to kill, go for it. For most of us, we want to focus on tests that help us sleep at night. We'll be using jest in this guide, mostly because it provides snapshot testing.

mapDispatchToProps

This isn't something people normally test, but it saves us from typos that only show up in response to user interaction. Often we fail to dispatch all of the actions in our manual testing of UIs.

The premise here is simple, we want to know that all of our keys are actual functions. We'll be using the shorthand form of mapDispatchToProps which is a simple object literal. This form has the benefits of saving you some boilerplate, and forcing the component to pass any data needed (e.g. from props) to the action creators directly. In order to test it, we'll need to export it from our component module.

export const mapDispatchToProps = {
  onFoo: fooAction,
};

export default connect(mapStateToProps, mapDispatchToProps)(C);

Then in our test we import this and assert that all of the properties are objects

import {mapDispatchToProps}, C from '../C';

const propertiesAreFunctions = (o) => Object.keys(o)
  .every(key => typeof o[key] === 'function');

it('mapDispatchToProps is sane', () => {
  expect(propertiesAreFunctions(mapDispatchToProps)).toBe(true);
});

Selectors

Selectors are little functions that take the entire state tree and return a piece of it. Sometimes they do moderately complex logic, merging multiple reducers into a single result.

The lazy way to test them is to pass a fake state object and see what they return. This provides almost no value! As with most traditional unit testing, all we're doing is repeating ourselves. Instead, we can test it against the real store.

To do this, we'll need a copy of our store without the middleware. Let's call this file 'createTestStore.js'

import {createStore} from 'redux';
import reducer from './reducer';

export default = () => createStore(reducer);

We also need a selector

export const mySelector = (state, key) => state.foo[key];

And finally, our unit test. Note that we also use jest's snapshot testing. For more complex results, this allows us to visually inspect the result of our selector. Snapshots help in code review, and figuring out when things break, without relying on us testing every single property of the selector's return value. This isn't strictly required, but it's a nice extra touch.

import {mySelector} from '../selectors';
import {fooAction} from '../actions';
import createTestStore from '../createTestStore';

it('selects stuff', () => {
  const store = createTestStore();
  store.dispatch(fooAction('bar', 1));
  const res = mySelector(store.getState(), 'bar')
  expect(res).toMatchSnapshot();
  expect(res).toBe(true);
});

You could argue that this test is doing too much. We have this mindset that unit tests should test one unit, which sounds good until you realize that your tests have no value. By expanding the tests a little we find real value and security in the correctness of our program.

Testing selectors in this way puts mapStateToProps below the threshold for mandatory testing.

TODO: write about sagas and stuff

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment