Testing Actions

Conceptual how to

The good news about testing actions is that you probably won’t have to do a lot of testing! Most Flux actions just pass data directly to the dispatcher, which then gets passed to the store to be handled.

However, there are instances where actions will modify the data in some way before passing it on to the dispatcher. We can test these by “spying on”/listening to the dispatcher and ensuring that alt.dispatcher.dispatch is called with the correct payload.

An action might also have other side effects such as calling another action, in which case we might also need to “spy” on that action to ensure that it is also called correctly.

Let’s jump into it with an example:

Example

You can also download and run this example.

Action

import alt from 'MyAlt';
import legalActions from 'actions/LegalActions';

class PetActions {
  constructor() {
    // these we do not need to test as we trust alt tests `generateActions`
    this.generateActions('buyPet', 'sellPet');
  }

  // this action modifies the dispatched data AND calls another action
  buyExoticPet({pet, cost}) {
    var importCost = 1000,
        illegalAnimals = ['dragon', 'unicorn', 'cyclops'],
        // adds import charge to cost
        totalCost = importCost + cost;

    return (dispatch) => {
      dispatch({
        pet,
        cost: totalCost
      });
      // checks if pet is legal
      if(illegalAnimals.indexOf(pet) >= 0) {
        legalActions.illegalPet(pet);
      }
    }

  }
}

export default alt.createActions(PetActions);

Action Test

import alt from 'MyAlt';
import petActions from 'actions/PetActions';
import legalActions from 'actions/LegalActions';
// you can use any assertion lib you want
import {assert} from 'chai';
// we will use [sinon](http://sinonjs.org/docs/) for spying, but you can use any similar lib
import sinon from 'sinon';

describe('PetActions', () => {
  beforeEach(() => {
    // here we use sinon to create a spy on the alt.dispatcher.dispatch function
    this.dispatcherSpy = sinon.spy(alt.dispatcher, 'dispatch');
    this.illegalSpy = sinon.spy(legalActions, 'illegalPet');
  });

  afterEach(() => {
    // clean up our sinon spy so we do not affect other tests
    alt.dispatcher.dispatch.restore();
    legalActions.illegalPet.restore();
  });

  describe('#buyExoticPet', () => {
    it('dispatches correct data', () => {
      var pet = 'moose',
          cost = 18.20,
          totalCost = 1000 + cost,
          action = petActions.BUY_EXOTIC_PET;

      // fire the action
      petActions.buyExoticPet({pet, cost});
      // use our spy to see what payload the dispatcher was called with
      // this lets us ensure that the expected payload (with US dollars) was fired
      var dispatcherArgs = this.dispatcherSpy.args[0];
      var firstArg = dispatcherArgs[0];
      assert.equal(firstArg.action, action);
      assert.deepEqual(firstArg.data, {pet, cost: totalCost});
    });

    it('does not fire illegal action for legal pets', () => {
      var pet = 'dog',
          cost = 18.20;

      // fire the action
      petActions.buyExoticPet({pet, cost});
      // use our spy to ensure that the illegal action was NOT called
      assert.equal(this.illegalSpy.callCount, 0);
    });

    it('fires illegal action for illegal pets', () => {
      var pet = 'unicorn',
          cost = 18.20;

      // fire the action
      petActions.buyExoticPet({pet, cost});
      // use our spy to ensure that the illegal action was called
      assert(this.illegalSpy.calledOnce, 'the illegal action was not fired');
    });
  });
});