How do we test asynchronous actions? The ones that depends on a API call that may or may not be resolved. This is what we are going to focus here: how to make sure your actions are called in order and how to intercept axios to not make actual API calls.
TL;DR: the complete code is in the end of the article!
Just to be clear this is what we are going to be using:
- Jest as a test framework;
- Moxios to mock API calls made by Axios
- redux-mock-store to mock our store
- Enzyme for testing utilities
- Redux Thunk for dispatching async actions
You are expected to know:
- Basic Redux
- Basic redux-thunk
- A little bit of testing, but not really a problem
- Promises, API calls…
Let’s begin talking about our stack. Moxios is a testing utility created by Axios with the intent to mock our calls, so we don’t hit the server every time we run our tests. If you look at the documentation you will see that it works by intercepting the calls when they are made and mocking the response. Later we will see what are the necessary configurations for that!
Redux-mock-store is exactly what the name implies. Although it should not be used to mock an actual store to use in a Provider (to test connected components, see this and this). The goal of the library is to test actions and middlewares. The mocked store stores (no pun intended) the actions called in an array for you to compare to what you expect it to be.
So let’s get to it!
First things first, this is the action we are going to test: loginUser. It accepts userData as a parameter which returns a dispatch function (thunk) which returns the axios call. This is important: to return the axios call(a promise), we will see why later.
The step by step is commented in the code, but basically the action hits an API which returns an JWT Token (more about that here), that we store it locally. The token is set to every call header that axios make for now on, so we have authorized credentials. Then we decode the token using jwt_decode and dispatch the setCurrentUser action, that makes our user authenticated to our app. If any exceptions happen, we catch it and send a getErrors action. If the double arrow function confuses you check this.
If you haven’t done it yet, install every module necessary using npm or yarn: axios, moxios, redux-mock-store, redux-thunk
Now let’s get testing!
First we will need to configure our mockStore. A good idea is to create a Utils file (or helpers, or common, hard to decide…) to put your store configuration, so you don’t need to create it in every test file you make.
So in our test file we will call it like this and pass our initial state
Now we are ready to begin our actual testing. Our frame of mind is: we need to test if the correct actions are called in the correct order. The reducer part should not be included in this testing, it should be tested in a separated reducer test file. So this is how our test file looks like
To make moxios work, you need to add the beforeEach and afterEach functions installing and unistalling for each test suite. Inside the test, you should declare the payload to send to your action (in this case it’s a user object) and specify what’s the response you expect from the API call. This is what moxios.wait() does: it waits for the API call your action sends, intercept it and mock the response with the passed information.
Moxios uses a status key to determine if it’s a successfull or failed call, and your expected response inside the response key.
When you use store.dispatch() it expects a promise back, that is why is necessary to return our axios call in our userAction, otherwise you will get an error
TypeError: Cannot read property ‘then’ of undefined
There are a few things you can do to optimize your tests and make it more elegant. This is our whole test file optimized. It tests both successful and failed API call and the actions called in each case.
You can see more about the redux-mock-store API in their github page, where maybe you’ll think about cleaning all your actions after each test with store.clearActions(), or check your mock store state with store.getState().
Redux-mock-store is also useful to test synchronous actions to make sure they are called, which you can also check out in their github page.
So that is it! I hope this helps you. See you in a next post!