And probably the most valuable tests you can write for a Redux app are action creator tests. Actions tend to collect most of the messy, complex code in a Redux app, and learning how to test them well can make future maintenance and refactoring easier for you and your team.
In this article, I’ll explain the pattern I use for testing Redux actions (asynchronous action creators in particular) and hopefully show that testing even complex actions can be pretty easy!
Packages to install
For action testing, I use the following packages:
mocha- my JS testing framework of choice. If you don’t use
mocha, however, keep reading - the strategies in this article are not
mochaspecific and can work with any JS testing framework.
nock- an HTTP mocking library. This is only used if your app is making HTTP requests (but at some point, your app probably will).
redux-mock-store- a simple, but effective Redux store mock.
I usually create a base
nock setup file in my app that I can import when needed. It looks something like this:
I recommend adding
nock.disableNetConnect() as it will force disable all non-mocked HTTP requests during your test. This ensures your tests won’t accidentally hit a real API endpoint during a test run, a bad practice that is both dangerous and slow.
An Example Action Creator
Let’s start with a look at a real world example, this set of action creators for fetching users from an API endpoint:
fetchUsers is the main action creator we want to test. When executed, it will send an HTTP request to the API, and dispatch two out of these three actions:
fetchUsersRequest- an action used to represent a request in progress (handy if your UI needs to show some kind of loading state)
fetchUsersSuccess- an action that fires if the API call succeeds
fetchUsersFailure- an action that fires if the API call fails
We want our test to check that these action creators are all invoked in the correct order, with the correct data, for both successful and unsuccessful API calls. To do this, we’ll do the following:
- Setup a mock response for our HTTP API endpoint. If the action makes a request to
/users/123, then we should make sure it hits our
nockserver and have
nockreturn a mock response that we can control and test against.
- Setup an ordered array of the actions we expect our action creator to dispatch and send to the reducers.
- Dispatch the action creator function, assert it received our mock data, and assert it called all of our expected actions in the correct order.
Testing The Happy Path
Given the strategy above, here is what testing the success path of
fetchUsers might look like:
Let’s break this test down into the important parts:
Here, we setup both our mock store and some fake data that we’ll use later for our mock HTTP response.
Next, we use
nock to create a mock HTTP route for our Redux app to hit. This will return back our mock user data (
this.usersData) whenever a successful GET request is made to the
Then we create an
expectedActions array, an array of the actions we expect to be dispatched in the order we expect them to be dispatched in. The first object is the action created when
fetchUsersRequest is invoked, and the second object is the action created when
fetchUsersSuccess is invoked. Note: the
expectedActions array here does not contain the action for
fetchUsersFailure as we are only testing the success path right now.
Finally, we kick off the test by dispatching our
fetchUsers action creator using our mock store’s
redux-mock-store comes with a handy
store.getActions function, so we can see which actions were called, and make sure it equals our
Testing The Not So Happy Path
We should also test that our action creators handle errors appropriately. Here is what testing the failure path of
fetchUsers might look like:
It has a very similar structure to the success test above. The main difference is our HTTP request mock now replies with a HTTP
500 status code (used for internal server errors) and our
expectedActions array includes the results from the
fetchUsersFailure action creator. Also, we run our
assert in the
catch portion of our promise (as we expect a failure here), and we throw an error in the success path in case our action creator incorrectly manages to succeed.
And that’s it! You just learned how to fully test an async action creator for a Redux app!
With this testing strategy, even as your action creators become more complex, the testing strategy itself stays the same. They still follow the same pattern of creating a mock HTTP response, creating a list of expected actions, and comparing both to the result of the action creator function call. I recommend giving this testing strategy a try, especially if you’ve never tested your Redux actions before - I think you’ll find them to be invaluable addition to your Redux app.