Test an asynchronous action with thunk and jest - reactjs

I can't test an asynchronous action that works with thunk, could one tell me what I'm doing wrong or how could I do it?
File containing the action I want to test: technology.js (action)
import { types } from "../types/types";
import swal from "sweetalert";
export const startFetchTechnologies = () => {
return async (dispatch) => {
try {
dispatch(startLoading());
const res = await fetch(
"http://url.com/techs"
);
const data = await res.json();
dispatch(loadTechnologies(data));
} catch (error) {
await swal("Error", "An error has occurred", "error");
}
dispatch(finishLoading());
};
};
export const loadTechnologies = (data) => ({
type: types.loadTechnologies,
payload: data,
});
export const startLoading = () => ({
type: types.startLoadTechnologies,
});
export const finishLoading = () => ({
type: types.endLoadTechnologies,
});
File containing the tests I want to perform: technology.test.js (test)
import { startFetchTechnologies } from "../../actions/technology";
import { types } from "../../types/types";
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import fetchMock from "fetch-mock";
import expect from "expect"; // You can use any testing library
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe("startFetchTechnologies", () => {
afterEach(() => {
fetchMock.restore();
});
beforeEach(() => {
jest.setTimeout(10000);
});
test("startFetchTechnologies", () => {
// fetchMock.getOnce("/todos", {
// body: { todos: ["do something"] },
// headers: { "content-type": "application/json" },
// });
const expectedActions = [
{ type: types.startLoadTechnologies },
{ type: types.loadTechnologies, payload: "asd" },
{ type: types.endLoadTechnologies },
];
const store = mockStore({});
return store.dispatch(startFetchTechnologies()).then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions);
});
});
});
The console outputs the following:
FAIL src/__test__/actions/technology.test.js (11.407 s)
startFetchTechnologies
✕ startFetchTechnologies (10029 ms)
● startFetchTechnologies › startFetchTechnologies
: Timeout - Async callback was not invoked within the 10000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 10000 ms timeout specified by jest.setTimeout.Error:
19 | });
20 |
> 21 | test("startFetchTechnologies", () => {
| ^
22 |
23 | // fetchMock.getOnce("/todos", {
24 | // body: { todos: ["do something"] },
I have tried increasing the timeout to 30000 and the test keeps failing.
I hope you can help me!

I have made the test pass but I am not sure if I am doing it correctly, could someone tell me if it is well done?
Thank you!
import { startFetchTechnologies } from "../../actions/technology";
import { types } from "../../types/types";
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import fetchMock from "fetch-mock";
import expect from "expect"; // You can use any testing library
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe("startFetchTechnologies", () => {
beforeEach(() => {
// jest.setTimeout(10000);
});
afterEach(() => {
fetchMock.restore();
});
test("startFetchTechnologies", () => {
fetchMock.getOnce("https://url.com/tech", {
body: { payload: ['asd'] },
headers: { "content-type": "application/json" },
});
const expectedActions = [
{ type: types.startLoadTechnologies },
{ type: types.loadTechnologies, payload: {payload: ['asd']} },
{ type: types.endLoadTechnologies },
];
const store = mockStore({});
return store.dispatch(startFetchTechnologies()).then(() => {
// return of async actions
expect(store.getActions()).toEqual(expectedActions);
});
});
});

Related

Redux Jest test fetchMock.getOnce not working

I'm using Redux, fetchMock, redux-mock-store to write a redux action test. Based on this document, https://redux.js.org/recipes/writing-tests, I write my one, but it seems the fetchMock is not working. The get request on the redux action uses the original value instead of fetchMock data.
Can anyone let me know where is wrong?
actions:
import { types } from "./types"
import axios from "axios"
import { getPosts } from "../apis"
export const fetchPosts = () => (dispatch) => {
return getPosts()
.then((res) => {
dispatch({
type: types.GET_POSTS,
payload: res.data
})
})
.catch((err) => {
// console.log(err);
})
}
test:
import moxios from "moxios"
import { testStore } from "./../../Utils"
import { fetchPosts } from "./../actions"
import configureMockStore from "redux-mock-store"
import thunk from "redux-thunk"
import fetchMock from "fetch-mock"
import { types } from "../actions/types"
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
describe("select_actions", () => {
afterEach(() => {
fetchMock.restore()
})
it("Dispatches the correct action and payload", () => {
const expectedState = [
{
title: "Example title 1",
body: "Some Text"
},
{
title: "Example title 2",
body: "Some Text"
},
{
title: "Example title 3",
body: "Some Text"
}
]
const store = mockStore({})
fetchMock.getOnce("https://jsonplaceholder.typicode.com/posts?_limit=10", {
body: expectedState,
headers: { "content-type": "application/json" }
})
const expectedActions = [{ type: types.GET_POSTS, payload: expectedState }]
return store.dispatch(fetchPosts()).then(() => {
expect(store.getActions()).toEqual(expectedActions)
})
})
})
API:
import axios from "axios"
export const getPosts = async () => await axios.get("https://jsonplaceholder.typicode.com/posts?_limit=10")

Async redux actions tests always return pending

I am writing tests for some async actions however the tests are failing because the type which is returned is always REQUEST_PENDING. So even for the tests when the data is fetched the type does not change and the test fails. I am not sure what I am doing wrong.
So the REQUEST_SUCCESS and REQUEST_FAILED are the tests that are always returning REQUEST_PENDING
This is my actions.js
import axios from 'axios';
import {
REQUEST_PENDING,
REQUEST_SUCCESS,
REQUEST_FAILED,
} from './constants';
export const setSearchField = (payload) => ({ type: SEARCH_EVENT, payload });
export const requestRobots = () => {
return async (dispatch) => {
dispatch({
type: REQUEST_PENDING,
});
try {
const result = await axios.get('//jsonplaceholder.typicode.com/users');
dispatch({ type: REQUEST_SUCCESS, payload: result.data });
} catch (error) {
dispatch({ type: REQUEST_FAILED, payload: error });
}
};
};
and this is my actions.test.js
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import {
REQUEST_PENDING,
REQUEST_SUCCESS,
REQUEST_FAILED,
} from './constants';
import * as actions from './actions';
const mock = new MockAdapter(axios);
const mockStore = configureMockStore([thunk]);
const payload = [
{
id: 1,
name: 'robocop',
email: 'robocop#gmail.com',
key: 1,
},
];
describe('handles requestRobots', () => {
beforeEach(() => {
// Runs before each test in the suite
store.clearActions();
});
const store = mockStore();
store.dispatch(actions.requestRobots());
const action = store.getActions();
it('Should return REQUEST_PENDING action', () => {
expect(action[0]).toEqual({
type: REQUEST_PENDING,
});
});
it('Should return REQUEST_SUCCESS action', () => {
mock.onGet('//jsonplaceholder.typicode.com/users').reply(200, {
data: payload,
});
return store.dispatch(actions.requestRobots()).then(() => {
const expectedActions = [
{
type: REQUEST_SUCCESS,
payload: {
data: payload,
},
},
];
expect(store.getActions()).toEqual(expectedActions);
});
});
it('Should return REQUEST_FAILURE action', () => {
mock.onGet('//jsonplaceholder.typicod.com/users').reply(400, {
data: payload,
});
return store.dispatch(actions.requestRobots()).then(() => {
const expectedActions = [
{
type: REQUEST_FAILED,
payload: {
data: ['Error: Request failed with status code 404'],
},
},
];
expect(store.getActions()).toEqual(expectedActions);
});
});
});
The lifecycle of a thunk action is that it will dispatch the REQUEST_PENDING action at the start of every call and then dispatch a REQUEST_FAILED or REQUEST_SUCCESS action at the end.
In your second and third test cases, the store.getActions() array actually has two elements: the pending action and the results action. You need to expect that the actions is an array with both. The REQUEST_FAILED and REQUEST_SUCCESS actions are there, but you aren't seeing them because they are the second element.
Define your pendingAction as a variable since you'll need it in all three tests.
const pendingAction = {
type: REQUEST_PENDING
}
Then include it in your expectedActions array.
const expectedActions = [
pendingAction,
{
type: REQUEST_SUCCESS,
payload: {
data: payload
}
}
]
This will cause your success test to pass. I did a quick run of the tests and the failure test still fails because it is not properly mocking the API failure. Right now it is returning a success because the requestRobots function uses the real axios object and not the mock axios adapter. But maybe something in your environment handles this differently.

axios.create of jest's axios.create.mockImplementation returns undefined

I have written a test for component CategoryListContainer for just testing axios get call in it by mocking axios as below :
CategoryListContainer.test.js
import React from 'react';
import { render, cleanup, waitForElement } from '#testing-library/react';
import { Provider } from 'react-redux';
import store from '../../Store';
import axios from 'axios';
import CategoryListContainer from './CategoryListContainer';
jest.mock('axios', () => ({
create: jest.fn(),
}));
const products = {
data: [
{
id: '0',
heading: 'Shirt',
price: '800',
},
{
id: '1',
heading: 'Polo tees',
price: '600',
},
],
};
afterEach(cleanup);
const renderComponent = () =>
render(
<Provider store={store()}>
<CategoryListContainer />
</Provider>
);
test('render loading state followed by products', async () => {
axios.create.mockImplementation((obj) => ({
get: jest.fn(() => Promise.resolve(products)),
}));
const { getByText } = renderComponent();
await waitForElement(() => {
expect(getByText(/loading/i)).toBeInTheDocument();
});
});
As we see that in test 'render loading state followed by products' I wrote mock implemenation for axios.create as axios.create.mockImplementation((obj) => ({ get: jest.fn(() => Promise.resolve(products)), }));
Now when I use axios.create in axiosInstance.js as below :
import axios from 'axios';
const axiosInstance = axios.create({
headers: {
Accept: 'application/json',
ContentType: 'application/json',
authorization: '',
},
});
console.log(axiosInstance);
export default axiosInstance;
console.log(axiosInstance) shows undefined and therefore I'm getting the below error on running the test :
TypeError: Cannot read property 'get' of undefined
4 | const fetchCategories = () => async (dispatch) => {
5 | const response = await axiosInstance
> 6 | .get('/api/category/all')
| ^
7 | .catch((error) => {
8 | dispatch(fetchErrorAction(error));
9 | if (error.message.split(' ').pop() == 504) {
console.log src/backendApiCall/axiosInstance.js:9
undefined
I want to understand why console.log(axiosInstance) shows undefined . And the solution to making the test successful with making minimum changes to code .
because 'create' return jest function so it does not has '.get'.
You can use this
jest.mock('axios', () => {
return {
create: () => {
return {
get: jest.fn()
}}
};
});
then, you can set mock value
axiosInstance.get.mockReturnValueOnce({
data : {}
})

Mock axios get request with queryParams with moxios

I am trying to test my async Redux action which gets data from Firebase.
I use jest and moxios to mock the async call
actionTypes.js
export const FETCH_ORDERS_START = 'FETCH_ORDERS_START'
export const FETCH_ORDERS_SUCCESS = 'FETCH_ORDERS_SUCCESS'
export const FETCH_ORDERS_FAILED = 'FETCH_ORDERS_FAILED'
order.js
import * as actionTypes from './actionTypes'
import axios from './../../axios-orders'
export const fetchOrdersSuccess = (orders) => {
return {
type: actionTypes.FETCH_ORDERS_SUCCESS,
orders: orders,
}
}
export const fetchOrdersFailed = (error) => {
return {
type: actionTypes.FETCH_ORDERS_FAILED,
error: error,
}
}
export const fetchOrdersStart = () => {
return {
type: actionTypes.FETCH_ORDERS_START,
}
}
export const fetchOrders = (token, userId) => {
return dispatch => {
dispatch(fetchOrdersStart())
const queryParams = `?auth=${token}&orderBy="userId"&equalTo="${userId}"`
axios.get('/orders.json' + queryParams)
.then(resp => {
const fetchedData = []
for (let key in resp.data) {
fetchedData.push({
...resp.data[key],
id: key,
})
}
dispatch(fetchOrdersSuccess(fetchedData))
})
.catch( error => dispatch(fetchOrdersFailed(error)))
}
}
In my test, i expect that calling fetchOrders(token, userId) will produce two redux actions: START and SUCCESS
import moxios from 'moxios';
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import * as actionTypes from './actionTypes';
import * as actions from './order'
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const ordersMock = {
"-LGyxbZUSr5Q4jboj0uw" : {
"ingredients" : {
"bacon" : 0,
"cheese" : 0,
"meat" : 1,
"salad" : 0
},
}
}
describe('order actions', () => {
beforeEach(function () {
moxios.install();
});
afterEach(function () {
moxios.uninstall();
});
it('creates FETCH_ORDER_SUCCESS after successfuly fetching orders', () => {
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: ordersMock,
});
});
const expectedActions = [
{ type: actionTypes.FETCH_ORDERS_START },
{ type: actionTypes.FETCH_ORDERS_SUCCESS, orders: ordersMock },
];
const store = mockStore({ posts: {} })
store.dispatch(actions.fetchOrders("TOKEN", "USER_ID"))
console.log(store.getActions())
expect(store.getActions()).toEqual(expectedActions);
})
})
Unfortunately it always seems to create SUCCESS and FAILED actions. How to properly mock axios call with queryParameters.
In fetchOrders is use my own axios instance with set base-name:
import axios from 'axios'
const instance = axios.create({
baseURL: 'https://urltofirebase.com'
})
export default instance
Here is the solution only use jestjs and typescript, without maxios module.
order.ts:
import * as actionTypes from './actionTypes';
import axios from 'axios';
export const fetchOrdersSuccess = orders => {
return {
type: actionTypes.FETCH_ORDERS_SUCCESS,
orders
};
};
export const fetchOrdersFailed = error => {
return {
type: actionTypes.FETCH_ORDERS_FAILED,
error
};
};
export const fetchOrdersStart = () => {
return {
type: actionTypes.FETCH_ORDERS_START
};
};
export const fetchOrders = (token, userId) => {
return dispatch => {
dispatch(fetchOrdersStart());
const queryParams = `?auth=${token}&orderBy="userId"&equalTo="${userId}"`;
return axios
.get('/orders.json' + queryParams)
.then(resp => {
dispatch(fetchOrdersSuccess(resp));
})
.catch(error => dispatch(fetchOrdersFailed(error)));
};
};
order.spec.ts:
import thunk, { ThunkDispatch } from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import * as actionTypes from './actionTypes';
import * as actions from './order';
import { AnyAction } from 'redux';
import axios from 'axios';
type State = any;
const middlewares = [thunk];
const mockStore = configureMockStore<State, ThunkDispatch<State, undefined, AnyAction>>(middlewares);
const ordersMock = {
'-LGyxbZUSr5Q4jboj0uw': {
ingredients: {
bacon: 0,
cheese: 0,
meat: 1,
salad: 0
}
}
};
describe('order actions', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('creates FETCH_ORDER_SUCCESS after successfuly fetching orders', () => {
expect.assertions(2);
const getSpy = jest.spyOn(axios, 'get').mockResolvedValueOnce(ordersMock);
const expectedActions = [
{ type: actionTypes.FETCH_ORDERS_START },
{ type: actionTypes.FETCH_ORDERS_SUCCESS, orders: ordersMock }
];
const store = mockStore({ posts: {} });
return store.dispatch(actions.fetchOrders('TOKEN', 'USER_ID')).then(() => {
expect(store.getActions()).toEqual(expectedActions);
expect(getSpy).toBeCalledWith('/orders.json?auth=TOKEN&orderBy="userId"&equalTo="USER_ID"');
});
});
});
Unit test result with coverage report:
PASS src/stackoverflow/51983850/order.spec.ts (7.79s)
order actions
✓ creates FETCH_ORDER_SUCCESS after successfuly fetching orders (8ms)
----------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------------|----------|----------|----------|----------|-------------------|
All files | 88.89 | 100 | 71.43 | 88.89 | |
actionTypes.ts | 100 | 100 | 100 | 100 | |
order.ts | 86.67 | 100 | 71.43 | 86.67 | 12,32 |
----------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 11.753s, estimated 19s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/51983850

React Redux async action tests

Hello I have some problems with testing react redux async actions every time i run the test I am receiving this array [{"type": "LOGIN"}] instead of this:
[{"type": "LOGIN"}, {"body": {"data": {"token": "1ca9c02f-d6d2-4eb8-92fd-cec12441f091", "userName": "8888888888888888"}}, "type": "LOGIN_SUCCESS"}]
Here are my code snippets:
The code from the actions:
export const LOGIN = 'LOGIN';
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_ERROR = 'LOGIN_ERROR';
import { Api } from '../../../constants/api';
const api = new Api();
function loginSuccess(data) {
return {
type: LOGIN_SUCCESS,
data,
};
}
function loginError(error) {
return {
type: LOGIN_ERROR,
error,
};
}
export function login(fields) {
return async dispatch => {
dispatch({ type: LOGIN });
api
.register(fields.vin)
.then(response => {
const data = Object.assign(response.data, fields);
return dispatch(loginSuccess(data));
})
.catch(error => dispatch(loginError(error)));
};
}
And the code from the action.test file:
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import fetchMock from 'fetch-mock';
import * as actions from './actions';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('async actions', () => {
afterEach(() => {
fetchMock.reset();
fetchMock.restore();
});
it('creates LOGIN_SUCCESS when fetching data has been done', () => {
fetchMock.getOnce('/register', {
body: {
data: {
userName: '8888888888888888',
token: '1ca9c02f-d6d2-4eb8-92fd-cec12441f091',
},
},
headers: { 'content-type': 'application/json' },
});
const expectedActions = [
{ type: actions.LOGIN },
{
type: actions.LOGIN_SUCCESS,
body: {
data: {
userName: '8888888888888888',
token: '1ca9c02f-d6d2-4eb8-92fd-cec12441f091',
},
},
},
];
const store = mockStore({ data: { id: null, token: null, userName: null } });
return store.dispatch(actions.login({ id: '8888888888888888' })).then(response => {
expect(store.getActions()).toEqual(expectedActions);
});
});
});
This is the first time testing async actions so I'm not sure what's going wrong.
You need to return the API promise since you are resolving the promise in your test case
return store.dispatch(actions.login({
id: '8888888888888888'
})).then(response => { // resolving Promise here
expect(store.getActions()).toEqual(expectedActions);
});
Your action must look like
export function login(fields) {
return dispatch => { // async is not needed here since no await is used
dispatch({ type: LOGIN });
return api // return here
.register(fields.vin)
.then(response => {
const data = Object.assign(response.data, fields);
return dispatch(loginSuccess(data));
})
.catch(error => dispatch(loginError(error)));
};
}

Resources