Multiple dispatchers in the action creators causing issues in unit test - reactjs

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().

Related

Redux-Thunk Action setting action as Promise

So I'm using TS React, Redux, and Thunk middleware to handle redux actions that communicate with my api but I cant seem to get the initial configuration for my action function.
My action function is as follows:
export const startSession = ((accessCode: string) => {
return async (dispatch: Dispatch): Promise<Action> => {
try {
const response = await apiCall(accessCode);
return dispatch({ type: SessionActions.START_SESSION, payload: response });
} catch (e) {
console.log('error', e)
}
};
});
I have also tried this:
export const startSession = ((accessCode: string) => {
return async (dispatch: Dispatch) => {
try {
await apiCall(accessCode)
.then(response => dispatch({ type: SessionActions.START_SESSION, payload: response }))
} catch (e) {
console.log('error', e)
}
};
})
but neither seems to work. I thought waiting for the api response would force redux to wait, but it seems to be returning the promise into the state - shown in my redux-logger:
action undefined # 19:10:17.807
redux-logger.js?d665:1 prev state: {some state}
redux-logger.js?d665:1 action: Promise {<pending>}
And I get the error:
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
I noticed that this dispatched type is undefined, so there must me a dispatch call being made initially before the data is returned from the api. If anyone could explain to me why it does this, and the standard format for writing actions that use thunk that would be super helpful.
Also please let me know if there is information that I'm missing.
Someone below asked to see how I the initialized store with thunk:
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { createLogger } from 'redux-logger';
import { createBrowserHistory } from 'history';
import rootReducer from '../_reducers/index'
const loggerMiddleware = createLogger();
export const history = createBrowserHistory();
export const store = createStore(
rootReducer,
applyMiddleware(
thunkMiddleware,
loggerMiddleware
)
);

how to retrieve response data when unit testing async redux action

I'm trying to make this test, test the getImages action function. I'm getting this error
● should getImages from action function › should getImages from
action function
expect(received).toEqual(expected) // deep equality
Expected: {"data": {}, "type": "GET_IMAGES"}
Received: [Function anonymous]
ideally I want to pass in the response data, in the data object. How would I successfully do this to make the test pass?
imageActions
import { GET_IMAGES, POST_COMMENT, DELETE_IMAGE, UPLOAD_IMAGE } from './types';
import Axios from '../Axios';
export const getImages = () => {
return dispatch => {
return Axios.get('/images/uploads').then(response => {
const data = response.data;
dispatch({
type: GET_IMAGES,
data
});
});
};
};
imageActions.test.js
import React from 'react';
import { shallow, mount } from 'enzyme';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { getImages } from './imageActions';
import { GET_IMAGES } from './types';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('should getImages from action function ', () => {
it('should getImages from action function', () => {
const expected = {
type: GET_IMAGES,
data: {}
};
const actual = getImages();
expect(actual).toEqual(expected);
});
});
Since you are using redux-mock-store, you should take a look at the examples of how to test async actions.
You will also have to mock the ajax request you are doing through your Axios module. As an example, I will use jest mock to mock the Axios module.
The code would look like:
import React from 'react';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { getImages } from './imageActions';
import { GET_IMAGES } from './types';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
// Mocking the Axios get method so that it returns something in data
jest.mock('../Axios', () => ({
get: jest.fn(() => Promise.resolve({ data: ['image1', 'image2'] }))
}));
describe('should getImages from action function ', () => {
it('should getImages from action function', () => {
const expected = {
type: GET_IMAGES,
data: ['image1', 'image2']
};
// Initialize mockstore with empty state
const store = mockStore({});
// Return the promise returned by store.dispatch so that the test waits until it's resolved.
return store.dispatch(getImages()).then(() => {
// Get the actions that have been dispatched
const actions = store.getActions();
// Check that the only action has the expected values.
expect(actions[0]).toEqual(expected);
});
});
});

Mocking `getAppState()` with Jest & Enzyme

I am trying to test my actions in my React / Redux application, and in one of my actions, I am using getAppState() to get the current state of Redux. The action has no parameters, and simply deconstructs state properties from the return of getAppState()
actions/myFile/index.test.js
it('should call myAction successfully', () => {
const expected = [
{
type: MY_TYPE,
payload: {
...mockPayload
}
}
];
store.dispatch(myAction());
expect(store.getActions().toEqual(expected));
});
actions/myFile/index.js
export const myAction = () => (dispatch, getAppState) => {
const { myReducer: { reducerProp } } = getAppState();
const { valOne, valTwo, valThree } = reducerProp;
return myServiceCallPromise({ valOne, valTwo }, valThree)
.then((res) => {
dispatch(anotherAction());
});
}
When trying to test my action with Jest, I'm getting an error stating:
TypeError: Cannot read property 'reducerProp' of undefined
My question is HOW do I mock what the getAppState() func returns and use them in my test? I have googled once, saw the results and figured, why not go to StackOverflow and ask myself lol
(I'm also assuming you are using redux-thunk).
You are going to need to configure your store:
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
const initialState = {
myReducer: { reducerProp: 'something here' }
};
const store = mockStore(initialState)
// Your code down here...
See here for more info about how to use the store.

testing redux thunk async actions give undefined instead of promise

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.

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