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,
});
});
};
Related
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 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'm switching my state to redux and ran into this error
TypeError: sourceSelector is not a function
I've pasted the code from the component and the action i'm dispatching, i think its something in mapDispatchToProps but not sure
component
componentDidMount() {
const { dispatch } = this.props;
dispatch(getTableData(this.props.apiUrl, this.state.limit, this.state.skip));
}
const mapStateToProps = ({tableData}) => ({
tableData,
});
function mapDispatchToProps (dispatch) {
return {
getTableData: (data) => dispatch(getTableData(data)),
}
}
export default connect(
mapStateToProps,
mapDispatchToProps,
{ getTableData }
)(SearchableTable);
action
import * as TYPES from './types';
import axios from 'axios';
export const getTableData = (url, limit, skip) => async dispatch => {
try {
dispatch({ type: TYPES.FETCH_TABLE_DATA_LOADING });
const response = await axios.post(url, {
limit: limit,
skip: skip,
});
await dispatch({
type: TYPES.FETCH_TABLE_DATA,
payload: response.data,
});
dispatch({ type: TYPES.FETCH_TABLE_DATA_FINISHED });
} catch (err) {
dispatch({ type: TYPES.INSERT_ERROR, payload: err.response });
}
};
Try this
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import { getTableData } from "actions";
componentDidMount() {
// Make sure you use this.props.getTable data and not the raw imported
// getTableData function
this.props.getTableData(this.props.apiUrl, this.state.limit, this.state.skip));
}
const mapStateToProps = state => ({
tableData: state.tableData
});
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
getTableData
},
dispatch
);
return connect(
mapStateToProps,
mapDispatchToProps
)(SearchableTable);
I'm trying to learn the MERN stack, I'm trying to create a simple shopping list app.
I have a simple api to get, post and delete items, this all works using Postman when testing the api.
Locally the get works and I can get items from the db.
When I try to add to the db I get this error.
(node:28550) UnhandledPromiseRejectionWarning: ValidationError: item validation failed: name: Path `name` is required.
[0] at new ValidationError (/Users/user/Documents/_Work/cd/MERN/merntest-redux-1/node_modules/mongoose/lib/error/validation.js:30:11)
[0] at model.Document.invalidate (/Users/user/Documents/_Work/cd/MERN/merntest-redux-1/node_modules/mongoose/lib/document.js:2080:32)
[0] at p.doValidate.skipSchemaValidators (/Users/user/Documents/_Work/cd/MERN/merntest-redux-1/node_modules/mongoose/lib/document.js:1943:17)
[0] at /Users/user/Documents/_Work/cd/MERN/merntest-redux-1/node_modules/mongoose/lib/schematype.js:933:9
[0] at _combinedTickCallback (internal/process/next_tick.js:131:7)
[0] at process._tickCallback (internal/process/next_tick.js:180:9)
[0] (node:28550) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
[0] (node:28550) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
As the api works in postman I think it must be something to do the redux I'm using.
ShoppingList js
import React, {Component} from 'react';
import uuid from 'uuid';
import {connect} from 'react-redux';
import {getItems, deleteItem, addItem} from '../actions/itemActions';
class ShoppingList extends Component{
componentDidMount(){
this.props.getItems()
}
onDeleteClick = (id) => {
this.props.deleteItem(id);
}
render(){
const {items} = this.props.item
return(
<div>
<button
onClick={() => {
const name = prompt('New Item')
if(name){
this.props.addItem(name)
}
}}
>
Add Item
</button>
<ul>
{items.map(({id,name}) =>{
return(
<li key={id}>
<button
onClick={this.onDeleteClick.bind(this, id)}
>
×
</button>
{name}
</li>
)
})}
</ul>
</div>
)
}
}
const mapStateToProps = state => ({
item: state.item
})
export default connect (mapStateToProps, {getItems, deleteItem, addItem})(ShoppingList)
itemActions
import axios from 'axios';
import {GET_ITEMS, ADD_ITEM, DELETE_ITEM, ITEMS_LOADING} from '../actions/types';
export const getItems = () => dispatch =>{
dispatch(setItemsLaoding());
axios
.get('/api/items')
.then(res =>
dispatch({
type: GET_ITEMS,
payload: res.data
})
)
}
export const addItem = (name) => dispatch =>{
axios
.post('/api/items', name)
.then(res =>
dispatch({
type: ADD_ITEM,
payload: res.data
})
)
}
export const deleteItem = (id) =>{
return{
type: DELETE_ITEM,
payload: id
}
}
export const setItemsLaoding = () => {
return{
type: ITEMS_LOADING
}
}
itemReducer.js
import {GET_ITEMS, ADD_ITEM, DELETE_ITEM, ITEMS_LOADING} from '../actions/types';
const initialState = {
items: [],
loading: false
}
export default function(state = initialState, action){
switch(action.type){
case GET_ITEMS:
return{
...state,
items: action.payload,
loading:false
}
case DELETE_ITEM:
return{
...state,
items: state.items.filter(item => item.id !== action.payload)
}
case ADD_ITEM:
return{
...state,
items: [...state.items, { name: action.payload}]
}
case ITEMS_LOADING:
return{
...state,
loading:true
}
default:
return state
}
}
models/Item.js (mongoose model)
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ItemSchema = new Schema({
name:{
type: 'String',
required: true
},
Date:{
type: Date,
default: Date.now
}
})
module.exports = Item = mongoose.model('item', ItemSchema);
routes/api/items.js
const express = require('express');
const router = express.Router();
const Item = require('../../models/Item');
router.get('/', (req, res) => {
Item.find()
.then(items => res.json(items))
.catch(err => console.log(err))
});
router.post('/', (req, res) => {
const newItem = new Item({
name: req.body.name
})
newItem
.save()
.then(item => res.json(item))
.catch(err => console.log(err))
})
router.delete('/:id', (req, res) =>{
Item.findById(req.params.id)
.then(item => item.remove().then(() => res.json({success:true})))
.catch(err => err.status(404).json({success:false}))
})
module.exports = router;
Okay, I did some debugging and here is the problem, you are sending the item name as the key with no value so the API cannot understand it.
Change this code:
export const addItem = (name) => dispatch =>{
axios
.post('/api/items', name)
.then(res =>
dispatch({
type: ADD_ITEM,
payload: res.data
})
)
}
to this code:
export const addItem = name => dispatch => {
const req = {
name,
};
axios.post('/api/items', req).then(res =>
dispatch({
type: ADD_ITEM,
payload: res.data,
})
);
};
I solved this problem by adding this code to routes/api/items.js:
var bodyParser = require("body-parser");
router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: true }));
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)));
};
}