testing redux thunk async actions give undefined instead of promise - reactjs

I have a function which creates actions
export function dispatchAction (type, payload) {
return dispatch => {
dispatch({type: type, payload: payload})
}
}
I am writing test for it
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import * as actions from './actions
const mockStore = configureMockStore([thunk])
const store = mockStore({})
describe('dispatch action', () => {
it('should return action based on type and payload', () => {
const type = 'TEST'
const payload = 'payload'
return store.dispatch(actions.dispatchAction(type, payload)).then(()
=> {
expect(store.getActions())
.toEqual({type, payload})
})
})
})
but I am getting the error that Cannot read property 'then' of undefined.

As per the docs:
Any return value from the inner function will be available as the
return value of dispatch itself.
You don't return anything in your dispatchAction, hence the error message. If you want a Promise, then you have to return a Promise.

Related

useEffect dispatch problem (you may need to add middleware)

API
import axios from "axios"
const url = "http://localhost:5000/posts"
export const fetchPosts = () => async () => await axios.get(url)
export const createPost = async (post) => await axios.post(url, post)
ACTION
export const fetchPosts = async (dispatch) => {
try {
const {data} = await api.fetchPosts()
dispatch({
type: types.FETCH_POSTS,
payload: data
})
} catch (err) {
console.error(err)
}
}
STORE
import { createStore, applyMiddleware } from 'redux'
import rootReducer from './index'
import thunk from 'redux-thunk'
export default function configureStore() {
return createStore(rootReducer, applyMiddleware(thunk))
}
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux'
import configureStore from './Redux/store/configureStore'
const store = configureStore();
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
I want to make get request to my posts by axios. There is no problem in post request but I can't get one.
useEffect(() => {
dispatch(fetchPosts())
}, [dispatch])
When I use this method it throws this error :
Error: Actions must be plain objects. Instead, the actual type was: 'Promise'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions.
There is no any syntax or import/export mistake.
Why even if I inserted redux thunk to store it throws me this middleware error ?
You need to update fetchPosts return a function
export const fetchPosts = () => async (dispatch) => {
try {
const {data} = await api.fetchPosts()
dispatch({
type: types.FETCH_POSTS,
payload: data
})
} catch (err) {
console.error(err)
}
}
export const fetchPosts = () => async () => await axios.get(url)
the function above returns a promise
try
export const fetchPosts = async () => {
let res = await axios.get(url)
return res;
}
There is a slight problem in your action. Action creators are functions that return actions. You want to return an action (In this case a function) from your action creator.
So your action shoule be:
export const fetchPosts = () => async (dispatch) => {
// Added this part ^^^^^^
try {
const {data} = await api.fetchPosts()
dispatch({
type: types.FETCH_POSTS,
payload: data
})
} catch (err) {
console.error(err)
}
}
Alternatively, you can make this change to your code:
useEffect(() => {
dispatch(fetchPosts)
// Removed the () ^
}, [dispatch])

"Actions must be plain objects" ... but it is

Sorry very common error, but in the test below don't understand how the offending action storeMgrAnnouncement(result) is not a plain object. The api call and the thunk action are mocked, but the offending action isn't.
home.test.js
/* not showing 3rd party imports */
import Home from '../../components/home';
import {getMgrAnnouncement} from "../../__mocks__/actions";
import {STORE_MGR_ANNOUNCEMENT} from "../../constants";
import {success} from "../../__fixtures__/announcementGet";
const mockStore = configureStore([thunk]);
describe('Home Page', () => {
var store = null;
const initialState = {};
beforeEach(() => {
store = mockStore(initialState);
shallow(<Home store={store} />);
});
it ('should store manager announcement after retrieving it', async () => {
await store.dispatch(getMgrAnnouncement());
expect(store.getActions()).toContainEqual({
type: STORE_MGR_ANNOUNCEMENT,
payload: success
});
});
__mocks__/actions/index.js
import { storeMgrAnnouncement } from '../../actions';
import { success } from '../../__fixtures__/announcementGet';
/* mock api call */
function announcementGet() {
return new Promise((resolve, reject) => {
process.nextTick(() => {
resolve(success)
})
})
}
/* mock thunk action */
export function getMgrAnnouncement() {
return function(dispatch, getState) {
return announcementGet()
.then(result => {
/*
ERROR: Actions must be plain objects. Use custom middleware for async actions.
*/
dispatch(storeMgrAnnouncement(result));
})
}
}
actions/index.js
import { STORE_MGR_ANNOUNCEMENT } from '../../constants';
export function storeMgrAnnouncement(result) {
return {
type: STORE_MGR_ANNOUNCEMENT,
payload: result
}
}
You are dispatching the result of getMgrAnnouncement()
store.dispatch(getMgrAnnouncement())
But that function returns a function:
function getMgrAnnouncement() {
return function(dispatch, getState) {
You must dispatch an object, not a function.

Cannot read property '.then' of undefined when testing async action creators with redux and react

I'm trying to write some test using react, redux-mock-store and redux, but I keep getting and error. Maybe because my Promise has not yet been resolved?
The fetchListing() action creator actually works when I try it on dev and production, but I'm having problems making the test pass.
error message
(node:19143) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 3): SyntaxError
(node:19143) [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.
FAIL src/actions/__tests__/action.test.js
● async actions › creates "FETCH_LISTINGS" when fetching listing has been done
TypeError: Cannot read property 'then' of undefined
at Object.<anonymous> (src/actions/__tests__/action.test.js:44:51)
at Promise (<anonymous>)
at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:169:7)
async actions
✕ creates "FETCH_LISTINGS" when fetching listing has been done (10ms)
action/index.js
// actions/index.js
import axios from 'axios';
import { FETCH_LISTINGS } from './types';
export function fetchListings() {
const request = axios.get('/5/index.cfm?event=stream:listings');
return (dispatch) => {
request.then(( { data } ) => {
dispatch({ type: FETCH_LISTINGS, payload: data });
});
}
};
action.test.js
// actions/__test__/action.test.js
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { applyMiddleware } from 'redux';
import nock from 'nock';
import expect from 'expect';
import * as actions from '../index';
import * as types from '../types';
const middlewares = [ thunk ];
const mockStore = configureMockStore(middlewares);
describe('async actions', () => {
afterEach(() => {
nock.cleanAll()
})
it('creates "FETCH_LISTINGS" when fetching listing has been done', () => {
nock('http://example.com/')
.get('/listings')
.reply(200, { body: { listings: [{ 'corpo_id': 5629, id: 1382796, name: 'masm' }] } })
const expectedActions = [
{ type: types.FETCH_LISTINGS }, { body: { listings: [{ 'corpo_id': 5629, id: 1382796, name: 'masm' }] }}
]
const store = mockStore({ listings: [] })
return store.dispatch(actions.fetchListings()).then((data) => {
expect(store.getActions()).toEqual(expectedActions)
})
})
})
store.dispatch(actions.fetchListings()) returns undefined. You can't call .then on that.
See redux-thunk code. It executes the function you return and returns that. The function you return in fetchListings returns nothing, i.e. undefined.
Try
return (dispatch) => {
return request.then( (data) => {
dispatch({ type: FETCH_LISTINGS, payload: data });
});
}
After that you will still have another problem. You don't return anything inside your then, you only dispatch. That means the next then gets undefined argument
I also know this is an old thread but you need to make sure you return the async action inside of your thunk.
In my thunk I needed to:
return fetch()
the async action and it worked
Your action creator should return a promise as shown below:
// actions/index.js
import axios from 'axios';
import { FETCH_LISTINGS } from './types';
export function fetchListings() {
return (dispatch) => {
return axios.get('/5/index.cfm?event=stream:listings')
.then(( { data } ) => {
dispatch({ type: FETCH_LISTINGS, payload: data });
});
}
};
I know this is an old thread. But I think the issue is that your action creator is not asynchronous.
Try:
export async function fetchListings() {
const request = axios.get('/5/index.cfm?event=stream:listings');
return (dispatch) => {
request.then(( { data } ) => {
dispatch({ type: FETCH_LISTINGS, payload: data });
});
}
}

Multiple dispatchers in the action creators causing issues in unit test

Following is my action creator with multiple dispatches
export function getAnalysisById(Id){
let url = APIEndpoints["getAnalysisById"];
var dataObject = {
id: Id
}
return dispatch => {
dispatch ({
type:LOADING_ANALYSIS,
payload : { isLoading : true}
});
const request = axios.post(url, dataObject);
dispatch ({
type:GET_ANALYSIS_BY_ID,
payload: request
});
};
}
Now following is the testing code written for the above action
var chai = require('chai');
var expect = chai.expect;
const actions = require('../../src/actions/index');
import { applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import nock from 'nock'
import ReduxPromise from 'redux-promise'
const middlewares = [ thunk,ReduxPromise ]
import configureStore from 'redux-mock-store'
const mockStore = configureStore(middlewares)
describe("All actions WITH Asynchronous call",function description(){
it('should return an action to get All Analysis by id', () => {
const store = mockStore({})
// Return the promise
return store.dispatch(actions.getAnalysisById('costnomics_Actual vs Budjected'))
.then(() => {
// const actionss = store.getActions()
console.log('store',store)
})
})
});
Running the above test case produces the following error,
TypeError: Cannot read property 'then' of undefined
When I pass plain objects as return value for the action creators instead of dispatcher, everything is working fine. But when mulitple dispatchers are returned from the action creator, it gives me the above error?. Why is this happening?
Your thunk function isn't actually returning a promise. You need to have something like return request at the end so that the promise is returned from the thunk, and from there returned from store.dispatch().

Sinon spy with isomorphic-fetch

I created a simple thunk action to get data from an API. It looks like this:
import fetch from 'isomorphic-fetch';
function json(response) {
return response.json();
}
/**
* Fetches booksfrom the server
*/
export function getBooks() {
return function(dispatch) {
return fetch("http://localhost:1357/book", {mode: "cors"})
.then(json)
.then(function(data) {
dispatch({
type: "GET_Books",
books: data
});
// This lets us use promises if we want
return(data);
});
}
};
Then, I wrote a test like this:
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import {getBooks} from '../../actions/getBooks';
import nock from 'nock';
import fetch from 'isomorphic-fetch';
import sinon from 'sinon';
it('returns the found devices', () => {
var devices = nock("http://localhost:1357")
.get("/book")
.reply(200,
{});
const store = mockStore({devices: []});
var spy = sinon.spy(fetch);
return store.dispatch(getBooks()).then(() => {
}).catch((err) => {
}).then(() => {
// https://gist.github.com/jish/e9bcd75e391a2b21206b
expect(spy.callCount).toEqual(1);
spy.retore();
});
});
This test fails - the call count is 0, not 1. Why isn't sinon mocking the function, and what do I need to do to make it mock the function?
You are importing fetch in your test file and not calling it anywhere. That is why call count is zero.
This begs the question of why you are testing that the action creator is called in the first place when the test description is "returns the found devices".
The main purpose of thunk action creators is to be an action creator which returns a function that can be called at a later time. This function that is called at a later time can receive the stores dispatch and state as its arguments. This allows the returned function to dispatch additional actions asynchronously.
When you are testing a thunk action creator you should be focus on whether or not the correct actions are dispatched in the following cases.
The request is made
The response is received and the fetch is successful
An error occurs and the fetch failed
Try something like the following:
export function fetchBooksRequest () {
return {
type: 'FETCH_BOOKS_REQUEST'
}
}
export function fetchBooksSuccess (books) {
return {
type: 'FETCH_BOOKS_SUCCESS',
books: books
}
}
export function fetchBooksFailure (err) {
return {
type: 'FETCH_BOOKS_FAILURE',
err
}
}
/**
* Fetches books from the server
*/
export function getBooks() {
return function(dispatch) {
dispatch(fetchBooksRequest(data));
return fetch("http://localhost:1357/book", {mode: "cors"})
.then(json)
.then(function(data) {
dispatch(fetchBooksSuccess(data));
// This lets us use promises if we want
return(data);
}).catch(function(err) {
dispatch(fetchBooksFailure(err));
})
}
};
Tests.js
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import fetchMock from 'fetch-mock' // You can use any http mocking library
import {getBooks} from '../../actions/getBooks';
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
describe('Test thunk action creator', () => {
it('expected actions should be dispatched on successful request', () => {
const store = mockStore({})
const expectedActions = [
'FETCH_BOOKS_REQUEST',
'FETCH_BOOKS_SUCCESS'
]
// Mock the fetch() global to always return the same value for GET
// requests to all URLs.
fetchMock.get('*', { response: 200 })
return store.dispatch(fetchBooks())
.then(() => {
const actualActions = store.getActions().map(action => action.type)
expect(actualActions).toEqual(expectedActions)
})
fetchMock.restore()
})
it('expected actions should be dispatched on failed request', () => {
const store = mockStore({})
const expectedActions = [
'FETCH_BOOKS_REQUEST',
'FETCH_BOOKS_FAILURE'
]
// Mock the fetch() global to always return the same value for GET
// requests to all URLs.
fetchMock.get('*', { response: 404 })
return store.dispatch(fetchBooks())
.then(() => {
const actualActions = store.getActions().map(action => action.type)
expect(actualActions).toEqual(expectedActions)
})
fetchMock.restore()
})
})

Resources