I've been trying to use redux for quite long time and I finally got the time to use it on a "real" project.

Because I'm not a stranger to webpack, npm, and alike I did not suffer to put together a small dev environment that does the job but I understand that most people suffer from this fatigue.

In my case, my particular pain point was to set up unit testing; specifically testing async actions.

Read the docs: action creators

If you are familiar with redux documentation you know that it is pretty good and it's getting better every day so I did just that, went to the docs and looked for the place where they cover testing.

The site actually has a great insight on how to do it, but the examples were tied up to mocha and expect and, in my case, I didn't want to use either. I feel more comfortable using something simpler like tape that does not pollute the global scope.

Non async actions are easy to test.

// src/actions.js
export function addTodo (todo) {
  return { type: 'ADD_TODO', todo: todo  }
}

// test/actions.js
import test from 'tape'
import * as actions from '../src/actions'

test('addTodo action creator', function () {
  t.plan(1)
  const payload = { id: null, title: 'The title!', body: 'The post body' }
  t.deepEqual(action.addTodo(payload), { type: 'ADD_TODO', todo: payload } )
})

Because an action creator is just a function that returns an object it is super easy to test, just import the functions in your test file, call them and expect (or should I say test) the result.

Async all the things

Async actions are a little bit trickier to test. In redux an async action can be accomplished using a middleware like redux-thunk. This middleware lets you return not an action but a function that will manually dispatch the action.

export function getPost(postId) {
  return function (dispatch, getState) {
    $
      .getJSON('/posts/' + posotId)
      .done(function (postData) {
        dispatch( { type: 'SHOW_POST', post: postData } )
      })
      .fail(function (postData) {
        dispatch( { type: 'SHOW_ERROR' } )
      })
  }
}

Because we don't expect a result but a certain (dispatch) function to be called with some parameters we need a way to check what's happening inside the returning function.

Tape doesn't provide us with a way to assert that, but luckily we have sinon for the job. Sinon let you spy functions, in other words, it allows you to replace a function with a new one that does the same job but adds some methods to query what happened.

// src/actions.js
export function getPost(postId) {
  return function (dispatch, getState) {
    $
      .getJSON('/posts/' + posotId)
      .done(function (postData) {
        dispatch( { type: 'SHOW_POST', post: postData } )
      })
      .fail(function (postData) {
        dispatch( { type: 'SHOW_ERROR' } )
      })
  }
}

// test/actions.js
import test from 'tape'
import test from '../src/configureStore'
import nock from 'nock' //for mocking http requests
import * as actions from '../src/actions'

test('getPost action creator', function () {
  t.plan(1)
  
  nock('http://localhost:300/posts/1') //nock let you mock http requests
    .get('/todos')
    .reply(200, { id: 1, title: 'Post Title' } )
    
  const store = configureStore()
  const dispatch = sinon.spy(store, 'dispatch')
  const fn = actions.getPost(1)
  
  fn(dispatch, store.getState)
    
  t.true(dispatch.calledWith({ type: 'SHOW_POST', post: { id: 1, title: 'Post Title' } }))
})

Let's have a closer look at what we did here, first we have our getPost function same as before. In order to test it we need a couple of things: a way to mock an http request, a store, and a way to spy the dispatch function.

Why do I need the store

The store is the central part of redux; it's where all the state of our app is being kept and the best part is that has only three methods: getState, dispatch and subscribe, we will let go subscribe and getState for now and focus on dispatch.

When using regular non-async action creators redux default behavior is to call dispatch with the result of the action creator. When the thunk middleware is applied to our store it changes this behavior and inverts the control by giving us references to the dispatch and getState methods so we can dispatch ourselves after doing IO.

Ok, too much theory, I understand if you are by now a little bit lost but it will make sense in a minute (I hope).

Because async actions are being handled by thunk middleware and because that middleware gives us the responsibility to dispatch, this is the method we need to spy in order to check if our -mocked- request ended up firing the correct action.

That's exactly what the line: const dispatch = sinon.spy(store, 'dispatch') does. It uses sinon spy feature to create a new function named dispatch that will be able to answer some questions like if it was called and what params where passed to it, like in the last line of the snippet: dispatch.calledWith({ type: 'SHOW_POST', post: { id: 1, title: 'Post Title' } }).

Voila, testing async actions without following the docs was pretty simple and along the way, we learned some nice things about redux internals.

Conclusion

Redux is so dead simple (not easy, but simple) that it makes easy not to follow the oficial guide and develop your own patterns with the tools you like.