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: "a_person"});const mostRecentUser = UserStore.getMostRecentUser();expect(mostRecentUser).toBe("a_person");});
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:
- Jest auto-mocks everything unless you tell it not to.
MyDispatcher
has been thoroughly mocked.- When the store calls the mock
MyDispatcher.register
method, Jest makes information about how it was called (including the arguments it was called with) available on itsmock
property. - The first argument of the first call to
MyDispatcher.register
is the registered callback.
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: "a_person"});const mostRecentUser = this.UserStore.getMostRecentUser();expect(mostRecentUser).toBe("a_person");});});
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: "a_person"});const mostRecentUser = this.UserStore.getMostRecentUser();expect(mostRecentUser).toBe("a_person");});});
There's an example repo here showing it in action.
Further reading: Test-driven React: How To Manually Mock Components