An alternative for testing Vuex Actions

I'm currently working on an application using Vue and Vuex. Vue is an application framework like React and Angular, and Vuex is a state management library like Redux.

Much of the heavy lifting in Vuex modules happens in Actions. Actions are asynchronous functions and which typically synchronously 'commit' 'mutations' to update the state of Vuex's data store.

When testing Actions, the Vuex documentation recommends mocking Vuex's 'commit' function. Mocking 'commit' can be tedious for complex actions which call multiple mutations. Also it makes refactoring more time consuming, because when mutations are refactored, the Actions which call them must be refactored, along with the both the Actions and mutations tests. This is necessary even if the final result in the store is the same.

A slight alternative is to not mock the commit function, and instead 'dispatch' the action and test the final values on the store itself. This is more of a functional test than a unit test in that it tests the result of the Actions rather than verifying intermediate 'commit' calls.

I implemented a simple example application using Vue-cli which fetchs an array of values using an endpoint from another one of my applications, and displays a count of the values. The values are fetched using an Vuex action:

async getYieldCount({ commit }) {
  const response = await fetch('https://yield.io/api/allYields.json', { mode: 'cors' });
  const json = await response.json();
  commit('setYieldCount', { yieldCount: json.length });
}

And here is the canonical test example:

it('unit tests `getYieldCount` by mocking commit and testing args set on commit', async () => {
  const commit = sinon.spy();
  await yields.actions.getYieldCount({ commit });
  expect(commit.firstCall.args[0]).to.equal('setYieldCount');
  expect(commit.firstCall.args[1]).to.deep.equal({ yieldCount: 5 });
});

This test doesn't update the state of the store. The 'commit' function is mocked and the parameters to the mocked function are tested to ensure the correct values are passed.

The following is an example test which calls dispatch and tests the values on the store:

it('tests `getYieldCount` by calling dispatch and testing results directly on the store', async () => {
  await Store.dispatch('getYieldCount');
  expect(Store.state.yields.yieldCount).to.equal(5);
});

In this case, the 'setYieldCount' mutation is called implicitly by the 'getYieldCount' action.

TL;DR

While the Vuex docs recommend unit testing Actions by mocking the 'commit' function, for complex Actions it can be beneficial to ensure that the final store is in the expected state. To implement a functional test for Actions, call 'dispatch' on the store, and test the final state of the store.