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:
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. 🚀