How to do it...

We want to test actions. Let's take a look at how we can execute those tests.

Since we've been working with our countries-and-regions example a lot, let's finish by testing (at least some of) its actions and thunks: getCountries() is a good example, and quite similar to getRegions(). It will be good to remember that particular code here, so let's take a look:

export const getCountries = () => async dispatch => {
try {
dispatch(countriesRequest());
const result = await getCountriesAPI();
dispatch(countriesSuccess(result.data));
} catch (e) {
dispatch(countriesFailure());
}
};

To begin with, it dispatches an action to mark that a request is being done. Then it waits for the result of a web service call; this will require mocking! Finally, if the call was successful, an action is dispatched, including the received list of countries. On a failed call, a different action is dispatched, but showing the failure.

Now let's consider the following—how can we deal with the API call? The world.actions.js source code directly imports getCountriesAPI() from a module, but Jest has a feature just for that: we can mock a full module, providing mocks or spies for whichever functions we desire, as follows:

// Source file: src/regionsApp/world.actions.test.js

/* @flow */

import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";

import {
getCountries,
COUNTRIES_REQUEST,
COUNTRIES_SUCCESS,
COUNTRIES_FAILURE
} from "./world.actions.js";

import { getCountriesAPI } from "./serviceApi";

let mockPromise;
jest.mock("./serviceApi", () => {
return {
getCountriesAPI: jest.fn().mockImplementation(() => mockPromise)
};

// continues...

Whenever the getCountries() function calls getCountriesAPI(), our mocked module will be used and a promise (mockPromise) will be returned; it's up to us to appropriately decide what should that promise be, and we'll make that choice depending on whether we want a test to fail or succeed.

Now that we have a way to intercept API calls and have them produce any result we want, we can move on to writing the actual tests.

Let's deal with the happy path first, in which the API call for countries is successful, with no problems. A test can be written in the following way:

// ...continued

describe("getCountries", () => {
it("on API success", async () => {
const fakeCountries = {
data: [{ code: "UY" }, { code: "AR" }, { code: "BR" }]
};
mockPromise = Promise.resolve(fakeCountries);

const store = configureMockStore([thunk])({});

await store.dispatch(getCountries());

const dispatchedActions = store.getActions();

expect(getCountriesAPI).toHaveBeenCalledWith();
expect(dispatchedActions.length).toBe(2);
expect(dispatchedActions[0].type).toBe(COUNTRIES_REQUEST);
expect(dispatchedActions[1].type).toBe(COUNTRIES_SUCCESS);
expect(dispatchedActions[1].listOfCountries).toEqual(
fakeCountries.data
);
});

// continues...

How is this code structured? 

  1. We initially define some data (fakeCountries) that will be returned by our mockPromise.
  2. Then we create a mock store, according to the redux-mock-store documentation; we are only using the thunk middleware in our case, but you may add more. In fact, in our original code, we followed thunk with logger, but that's not relevant for our testing.
  3. After that, we store.dispatch() the getCountries() thunk and await its results.
  4. Once everything is done, we use store.getActions() to get the list of actions that were actually dispatched.
  5. We test that our getCountriesAPI() function was called; if it hasn't been, we'll be in deep trouble!
  6. Finally, we test all of the dispatched actions, checking their type and other attributes. This is, in fact, an indirect test on the action creators themselves!

Now that we've looked at a successful case, let's simulate that the API call somehow failed. To simulate this, all we have to do is define a different promise for the getCountriesAPI() call to return:

// ...continued

it("on API failure", async () => {
mockPromise = Promise.reject(new Error("failure!"));

const store = configureMockStore([thunk])({});

await store.dispatch(getCountries());

const dispatchedActions = store.getActions();

expect(getCountriesAPI).toHaveBeenCalledWith();
expect(dispatchedActions.length).toBe(2);
expect(dispatchedActions[0].type).toBe(COUNTRIES_REQUEST);
expect(dispatchedActions[1].type).toBe(COUNTRIES_FAILURE);
});
});

// continues...

What's different in this case? Our mockPromise is now set to fail, so the tests for the second dispatched actions vary: in this case, instead of success and a list of countries, we just get a failure—but the rest of the test is essentially the same.

Let's finish with an extra case. When we coded our thunks, we saw that we could access the current state by means of a getState() function and act differently depending on its contents. We could have coded our getCountries() function to avoid doing an API call if the list of countries had already been obtained, for a small optimization; the key part would have been as follows:

// ...continued

export const getCountries = () => async (dispatch, getState) => {
if (getState().countries.length) {
// no need to do anything!
} else {
try {
dispatch(countriesRequest());
const result = await getCountriesAPI();
dispatch(countriesSuccess(result.data));
} catch (e) {
dispatch(countriesFailure());
}
}
};

// continues...

How could we test this case? The difference would be in how we set up the store, and what actions actually get dispatched:

// ...continued

describe("optimized getCountries", () => {
it("doesn't do unneeded calls", async () => {
const store = configureMockStore([thunk])({
countries: [{ land: 1 }, { land: 2 }]
});

jest.resetAllMocks();

await store.dispatch(getCountries());

expect(getCountriesAPI).not.toHaveBeenCalled();
expect(store.getActions().length).toBe(0);
});
});

When we set up the store, we can provide it with initial values, as in this case, in which we make believe that some countries (fake data!) are already loaded. A special requirement: we must use jest.resetAllMocks(), because otherwise we won't be able to check that getCountriesAPI() wasn't called because it was called, but by the previous tests. Then, after dispatching the thunk, we just check that the API wasn't called and that zero actions were dispatched: everything's OK!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.107.241