Writing Epic Unit Tests

unit testing epics when using redux-observable

In my last post I talked about what redux-observable middleware is, and how you might apply it to solve a practical problem (autosave).

In this follow-up post, I’ll show you how easy and fun it is to do some epic unit testing! (this, and all further puns totally intended) As a quick reminder, this is roughly what the UI looked like in the last post, while the saveFieldEpic.js epic was listening for actions dispatched from this input component’s onChange event:

autosaving a field as the user types

Before we starting writing the code for our tests, we can make testing easier by injecting dependencies into all epics (that we’ll want to replace later in our tests) when we combine epics, right before calling createEpicMiddleware (full credit to Jay Phelps for this info!).

const rootEpic = (...args) => combineEpics(
saveFieldEpic,
)(...args, {ajax});

In this example, when we’re creating the Redux store, we inject ajax from rxjs/observable/dom/ajax which will now appear as the third argument in our epics (right after action$ and store). Again, this is so we can replace it with another function during testing that will not actually send an XHR over the network.

The code in our saveFieldEpic.js file from the last post will now look more like this, where we pass action$ on line 14 as usual, then null for the store value (since we won’t need it), and finally {ajax}.

*note: using null for the store value might lead to build errors, in which case you can just use store (updated Nov. 23, 2017)

Now that we’ve injected the AJAX dependency, and our epic is already operating in isolation, we can go about testing the inputs and outputs of the entire epic function itself (without having to mock the store).

By importing ActionsObservable from the redux-observable library, we can dispatch mock actions into the epic under test. Next we can create our mock version of the AJAX function, which will either return Observable.of({}) (or whatever kind of server response you’d like) if the AJAX call should be successful, or Observable.throw('something bad!') if the AJAX call should fail.

Finally, we import the epic, feed it that input, and call toArray on it so that when we subscribe to the epic’s output it will return an array of every action that epic produced as output! Asserting that actualOutputActions deeply equals expectedOutputActions gives us our passing tests!

Here’s how that would look in code:

I think the simplicity of this is a beautiful thing! I’ve also been finding that this technique remains super powerful when used in even more complicated scenarios. And, not least of all, these tests are a ton of fun to write. 🚀