Testing Flux Stores without Jest

Flux stores present an interesting unit testing challenge. From the docs:

By design, stores can't be modified from the outside. They have no setters. The only way new data can enter a store is through the callback it registers with the dispatcher.

To unit test a Flux store without having an entire Flux system in place, you need to be able to call the registered callback directly with a fake action payload. This updates the store's internal state, which you can then make assertions against using the store's public getter methods.

Something like this:

it("adds a user", function () {
  registeredCallbackFromStore({
    actionType: "ADD_USER",
    username: "clonehighrulez"
  });
  const mostRecentUser = UserStore.getMostRecentUser();
  expect(mostRecentUser).toBe("clonehighrulez");
});

But that registered callback is defined privately inside the store. How do we get a reference to it from our test?

Using Jest

The Flux docs show how to get the registered callback when you write tests with Jest using "this one weird trick":

callback = MyDispatcher.register.mock.calls[0][0];

The docs do a good job of explaining what's happening there, but for the uninitiated:

Using another test framework

If you're using another framework that doesn't do Jest's magical auto-mocking, you can still access the registered callback using the same weird trick. You just need to manually mock things yourself, taking care to mock & require in the correct order.

(This example uses Jasmine, but you could accomplish the same thing with Mocha or your framework of choice.)

beforeEach(function () {
  const MyDispatcher = require("my_dispatcher");
  spyOn(MyDispatcher, "register");
  this.UserStore = require("user_store");
  this.registeredCallback = MyDispatcher.register.calls.mostRecent().args[0];
});

describe("UserStore", function () {
  it("adds a user", function () {
    this.registeredCallback({
      actionType: "ADD_USER",
      username: "clonehighrulez"
    });
    const mostRecentUser = this.UserStore.getMostRecentUser();
    expect(mostRecentUser).toBe("clonehighrulez");
  });
});

But there's an easier way to get that registered callback...

Modifying modules with rewire

rewire is very cool. It works exactly the same as a plain old CommonJS require, except it...

adds a special setter and getter to modules so you can modify their behaviour for better unit testing.

Those special __set__ and __get__ methods let you go in and set or get anything in the (usually private) top level scope of the rewired module.

So all you need to do to get a reference to the store's registered callback is to go in and __get__ it:

beforeEach(function () {
  this.UserStore = rewire("../user_store");
  this.registeredCallback = this.UserStore.__get__("registeredCallback");
});

describe("UserStore", function () {
  it("adds a user", function () {
    this.registeredCallback({
      actionType: "ADD_USER",
      username: "clonehighrulez"
    });
    const mostRecentUser = this.UserStore.getMostRecentUser();
    expect(mostRecentUser).toBe("clonehighrulez");
  });
});

There's an example repo here showing it in action.

Further reading: Test-driven React: How To Manually Mock Components

Comments