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)));
};
}
Related
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")
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.
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');
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);
});
});
});
I create a simple case to test my actions, but my store.dispatch is not returning a promise. Can someone tell me what is wrong?
Action.js code:
export const handleSubmit = inputData =>
(dispatch) => {
axios.post(`${API_URL}/process-input/`, { input: inputData })
.then((resp) => {
dispatch({
type: 'UPDATE_OUTPUT',
payload: resp.data,
});
})
.catch((e) => {
dispatch({
type: 'UPDATE_OUTPUT',
payload: e.message,
});
});
};
And my test:
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import moxios from 'moxios';
import * as actions from './../../../../src/modules/inputData/action';
import { API_URL } from './../../../../src/constants';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('InputData actions', () => {
test('Test input value update', () => {
moxios.install();
moxios.stubRequest(`${API_URL}/process-input/`, { status: 200, response: 'A nice test result' });
const store = mockStore();
return store.dispatch(actions.handleSubmit('anyData'))
.then(() => {
expect(store.getActions()).toEqual([
{ type: 'UPDATE_OUTPUTT', payload: 'A nice test result' },
]);
});
});
});
The error that is returning is: Cannot read property 'then' of undefined
Your redux action is not returning a promise. You should return the axios.post function.
export const handleSubmit = inputData =>
(dispatch) => {
return axios.post(`${API_URL}/process-input/`, { input: inputData })
.then((resp) => {
dispatch({
type: 'UPDATE_OUTPUT',
payload: resp.data,
});
})
.catch((e) => {
dispatch({
type: 'UPDATE_OUTPUT',
payload: e.message,
});
});
};