Redux Jest test fetchMock.getOnce not working - reactjs

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")

Related

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.

Loading data from MongoDB into React State

I have a problem where I can load the user data from my node server, but when I try to get the data into State in the frontend of React, I get a 404 when I call the data.
error: http://localhost:3000/users 404 (Not Found)
I have tried several approaches but it seems that my issue lies in not being able to pre-load the data from the database into State....can anyone please tell me what I'm missing?
Routes/API
// #route GET api/users
// #desc Get Users
// #access Public
router.get('/', async (req, res) => {
try {
const users = await User.find();
res.json(users);
} catch (error) {
console.error(error.message);
res.status(500).send('Server Error');
}
});
module.exports = router;
Then on the frontend, I have an action
import axios from 'axios';
import {
GET_USERS,
GET_USERS_ERROR
} from './types';
// Get users
export const getUsers = () => async (dispatch) => {
try {
const res = await axios.get('/users');
dispatch({
type: GET_USERS,
payload: res.data,
});
} catch (error) {
dispatch({
type: GET_USERS_ERROR,
payload: {
msg: error.response.status.statusText,
status: error.response.status,
},
});
}
};
My reducer file:
import {
GET_USERS,
GET_USERS_ERROR
} from '../actions/types';
const initialState = {
user: null,
users: [],
error: {},
};
export default function(state = initialState, action) {
const {
type,
payload
} = action;
switch (type) {
case GET_USERS:
return {
...state,
users: payload,
};
case GET_USERS_ERROR:
return {
...state,
error: payload,
};
default:
return state;
}
}
finally, the place where I'm trying to get the data
import React, {
useState,
useEffect
} from 'react';
import {
connect
} from 'react-redux';
import {
getUsers
} from '../../actions/users';
import PropTypes from 'prop-types';
//Bootstrap Table
import BootstrapTable from 'react-bootstrap-table-next';
import paginationFactory from 'react-bootstrap-table2-paginator';
import * as ReactBootStrap from 'react-bootstrap';
const UserTable = ({
getUsers,
users
}) => {
useEffect(() => {
getUsers();
// eslint-disable-next-line
}, []);
const [loading, setLoading] = useState(false);
const columns = [{
dataField: '_id',
text: 'ID'
},
{
dataField: 'user_id',
text: "User's ID"
},
{
dataField: 'firstname',
text: 'Title of Todo'
},
{
dataField: 'lastname',
text: 'Is this done?'
},
];
return ( <
div > Hello < /div>
// <BootstrapTable
// keyField='id'
// data={users}
// columns={columns}
// pagination={paginationFactory()}
// />
);
};
const mapStateToProps = (state) => ({
users: state.users,
});
export default connect(mapStateToProps, {
getUsers
})(UserTable);
Based on this bit in you question // #route GET api/users, indicates you are likely missing /api in the FE call.
Try
const res = await axios.get('/api/users');

Test an asynchronous action with thunk and jest

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);
});
});
});

axios-mock-adapter onGet mock data not effective

I'm trying to test axios call with axios-mock-adapter. I encountered following issue:
The API calls from the test always respond to the real data instead of my mocked one with mock.onGet.
a.k. receivedActions always from the real API call, but not the expectedActions which mocked with mock.onGet.
Here is the action code (searchAction.js):
import { SEARCH_PHOTOS, FEATURED_PHOTOS } from './types';
import axiosInstence from '../apis/axiosInstence';
export const searchPhotos = (term) => (dispatch) => {
dispatch({ type: 'SEACH_REQUEST' });
return axiosInstence.get('/search/photos', {
params: {
query: term,
page: 1
}
}).then(response => {
dispatch({
type: 'SEARCH_PHOTOS',
payload: response.data
});
}).catch(error => {
dispatch({ type: 'SEACH_FAILURE' });
});
}
And my test looks like this (searchAction.test.js):
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import { searchPhotos } from '../searchAction';
const mock = new MockAdapter(axios);
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const term = 'cars';
const store = mockStore({});
describe('actions', () => {
beforeEach(() => {
mock.reset();
store.clearActions();
});
it('Should create an action to signIn with a fake user', async () => {
const expectedActions = [{
type: 'SEACH_REQUEST'
}, {
type: 'SEARCH_PHOTOS',
payload: []
}];
mock.onGet('/search/photos', {
params: { term: 'cars' }
}).reply(200, expectedActions);
await store.dispatch(searchPhotos(term))
.then(data => {
const receivedActions = store.getActions();
expect(receivedActions).toEqual(expectedActions);
});
});
});
Anybody have experienced similar issue or could give me some advise.
Thanks in advence.
There are a couple of problems in your code:
First, in the action creator you are using an axios instance to make the ajax call, but in the test you are not providing that instance to the axios-mock-adapter. You should provide your axios instance in your test when you create the instance of MockAdapter.
Second, the params property you are providing to the axios mock in the onGet method does not match the parameters that are sent in the get operation in your action creator. You should match the parameters in the call with their values. Thus, you should provide query and page params.
Last, you are returning the expectedActions in the mock request, but that does not seem right. Looking at your code, it seems that you want to return an empty array.
Having all that into account, your code would look like:
import MockAdapter from 'axios-mock-adapter';
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import axiosInstence from '../../apis/axiosInstence';
import { searchPhotos } from '../searchAction';
const mock = new MockAdapter(axiosInstence);
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const term = 'cars';
const store = mockStore({});
describe('actions', () => {
beforeEach(() => {
mock.reset();
store.clearActions();
});
it('Should create an action to signIn with a fake user', async () => {
const expectedActions = [{
type: 'SEACH_REQUEST'
}, {
type: 'SEARCH_PHOTOS',
payload: []
}];
mock.onGet('/search/photos', {
params: {
query: 'cars',
page: 1
}
}).reply(200, []);
const data = await store.dispatch(searchPhotos(term));
const receivedActions = store.getActions();
expect(receivedActions).toEqual(expectedActions);
});
});

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