0

I'm building an app with React, Facebook's Dispatcher and Flux Utils I'm using karma, mocha, sinon, browserify for testing Flux stores. Here is my snippet for testing:

import {expect} from 'chai';
import sinon from 'sinon';

describe('Store', function() {

  let registerSpy, Store, registeredCallback, isDispatchingStub;

  beforeEach(function(done) {
    const Dispatcher = require('../../../dispatcher');
    registerSpy = sinon.spy(Dispatcher, 'register');
    Store = require('../store');
    registeredCallback = registerSpy.lastCall.args[0];
    isDispatchingStub
      = sinon.stub(Dispatcher, "isDispatching", function() { return true; });
    done();
  });

  afterEach(function(done) {
    registerSpy.restore();
    isDispatchingStub.restore();
    done();
  });

  it('should update its state on message', function(done) {
    registeredCallback({
      type: 'MESSAGE',
      payload: {
        message: 'Our message'
      }
    });
    let state = Store.getState();
    expect(state.size).to.equal(1);
    done();
  });

});

Everything works fine if I have only one test executing. If I add another test I get an error since stores aren't reloaded when they are required (I don't get a fresh copy of the store, it already has some state after previous tests). So, registerSpy doesn't have a lastCall property because dispatcher has already registered the store. The same thing with Dispatcher, other stores can see dispatched actions, so, it is troublesome to test stores properly.

I've tried using rewire but it doesn't have browserify support. I've also tried cancelling require.cache but Browserify's require doesn't have cache property like node's one has.

How can I get a fresh copy of a module with browserify?

Vitaly Ivanov
  • 55
  • 1
  • 6

1 Answers1

0

So, I've tried a bunch of things. First thing, I've realized I don't need browser to test stores, they don't operate DOM. So, I used the solution with deleting require.cache. But, like mentioned here it can cause problems like infinite loops between dependencies. The first thing that happened with my tests was the infinite loop :).

This answer also says that you can wrap functionality in a function.

I had been exporting an instance from store files like here. I exported the class from the store and created an instance in the test file. The same thing with the dispatcher. So now, I have new clean store and dispatcher on each test.

Store example (now I export the store class and the instance):

import {ReduceStore} from 'flux/utils';
import Immutable from 'immutable';
import Dispatcher from '../../dispatcher';

export class AppMessageStore extends ReduceStore {

  getInitialState() {
    return Immutable.List();
  }

  reduce(state, action) {
    switch (action.type) {
      case 'APP_MESSAGE':
        return Immutable.List([action.payload.message]);

      default:
        return state;
    }
  }

}

const instance = new AppMessageStore(Dispatcher);
export default instance;

Test file (now I get clean dispatcher right from flux utils. A new store is created on every test, so every test has its own stores).

import {expect} from 'chai';
import sinon from 'sinon';
import {AppMessageStore} from '../index';
import {Dispatcher} from 'flux';

describe.only('AppMessageStore', function() {

  let registerSpy, appMessageStore, registeredCallback, isDispatchingStub;

  beforeEach(function(done) {
    const MyDispatcher = new Dispatcher();
    registerSpy = sinon.spy(MyDispatcher, 'register');
    appMessageStore = new AppMessageStore(MyDispatcher);
    registeredCallback = registerSpy.lastCall.args[0];
    isDispatchingStub
      = sinon.stub(MyDispatcher, "isDispatching", function() { return true; });
    done();
  });

  afterEach(function(done) {
    registerSpy.restore();
    isDispatchingStub.restore();
    done();
  });

  it('should update its state on message', function(done) {
    registeredCallback({
      type: 'APP_MESSAGE',
      payload: {
        message: 'Our message'
      }
    });
    let state = appMessageStore.getState();
    console.log('state.toObject()', state.toObject());
    expect(state.size).to.equal(1);
    done();
  });

  it('should blabla', function(done) {
    registeredCallback({
      type: 'APP_MESSAGE',
      payload: {
        message: 'New message'
      }
    });
    let state = appMessageStore.getState();
    console.log('state.toObject()', state.toObject());
    done();
  });

});
Community
  • 1
  • 1
Vitaly Ivanov
  • 55
  • 1
  • 6