how to write test cases for redux async actions using axios? - javascript

Following is a sample async action creator.
export const GET_ANALYSIS = 'GET_ANALYSIS';
export function getAllAnalysis(user){
let url = APIEndpoints["getAnalysis"];
const request = axios.get(url);
return {
type:GET_ANALYSIS,
payload: request
}
}
Now following is the test case I have wrote:
describe('All actions', function description() {
it('should return an action to get All Analysis', (done) => {
const id = "costnomics";
const expectedAction = {
type: actions.GET_ANALYSIS
};
expect(actions.getAllAnalysis(id).type).to.eventually.equal(expectedAction.type).done();
});
})
I am getting the following error:
All actions should return an action to get All Analysis:
TypeError: 'GET_ANALYSIS' is not a thenable.
at assertIsAboutPromise (node_modules/chai-as-promised/lib/chai-as-promised.js:29:19)
at .<anonymous> (node_modules/chai-as-promised/lib/chai-as-promised.js:47:13)
at addProperty (node_modules/chai/lib/chai/utils/addProperty.js:43:29)
at Context.<anonymous> (test/actions/index.js:50:5)
Why is this error coming and how can it be solved?

I suggest you to take a look at moxios. It is axios testing library written by axios creator.
For asynchronous testing you can use mocha async callbacks.
As you are doing async actions, you need to use some async helper for Redux. redux-thunk is most common Redux middleware for it (https://github.com/gaearon/redux-thunk). So assuming you'll change your action to use dispatch clojure:
const getAllAnalysis => (user) => dispatch => {
let url = APIEndpoints["getAnalysis"];
const request = axios.get(url)
.then(response => disptach({
type:GET_ANALYSIS,
payload: response.data
}));
}
Sample test can look like this:
describe('All actions', function description() {
beforeEach("fake server", () => moxios.install());
afterEach("fake server", () => moxios.uninstall());
it("should return an action to get All Analysis", (done) => {
// GIVEN
const disptach = sinon.spy();
const id = "costnomics";
const expectedAction = { type: actions.GET_ANALYSIS };
const expectedUrl = APIEndpoints["getAnalysis"];
moxios.stubRequest(expectedUrl, { status: 200, response: "dummyResponse" });
// WHEN
actions.getAllAnalysis(dispatch)(id);
// THEN
moxios.wait(() => {
sinon.assert.calledWith(dispatch, {
type:GET_ANALYSIS,
payload: "dummyResponse"
});
done();
});
});
});

I found out it was because, I had to use a mock store for the testing along with "thunk" and "redux-promises"
Here is the code which made it solve.
const {expect} = require('chai');
const actions = require('../../src/actions/index')
import ReduxPromise from 'redux-promise'
import thunk from 'redux-thunk'
const middlewares = [thunk,ReduxPromise]
import configureStore from 'redux-mock-store'
const mockStore = configureStore(middlewares)
describe('store middleware',function description(){
it('should execute fetch data', () => {
const store = mockStore({})
// Return the promise
return store.dispatch(actions.getAllDashboard('costnomics'))
.then(() => {
const actionss = store.getActions()
console.log('actionssssssssssssssss',JSON.stringify(actionss))
// expect(actionss[0]).toEqual(success())
})
})
})

Related

how to unit test if handler function is called when using middy

im using an http request function as the handler function in middy and then use the ssm middleware to fetch some ssm parameters before initiating the http request.
like this:
const makeThirdPartyServiceRequest = middy(async ({ params }) => {
logger.info(`SENDING Request to ${endpoint} API`)
const url = `https://someurltoathirdpartyservice`
const options = {
method: 'POST',
body: params
}
return helpers.makeRequest(url, options)
})
makeThirdPartyServiceRequest.use(ssm(......))
However in my jest unit test Im trying to mock makeThirdPartyServiceRequest and explicitly say it should resolve to a value:
jest.mock('../src/thirdPartyService', () => ({
__esModule: true,
default: {
...(jest.requireActual('../src/thirdPartyService') as { default: {} }).default,
makeThirdPartyServiceRequest: jest.fn()
}
}))
export {}
import thirdPartyService from '../src/thirdPartyService'
And then in the test i say:
describe('makeThirdPartyServiceRequest()', () => {
it('should makeThirdPartyServiceRequest', async () => {
// Given
// })
const mockedThirdPartyServiceRequest = mocked(thirdPartyService.makeThirdPartyServiceRequest).mockResolvedValue({})
// When
const result = await thirdPartyService.makeThirdPartyServiceRequest(something)
// Then
expect(mockedThirdPartyServiceRequest).toHaveBeenCalledTimes(1)
expect(mockedThirdPartyServiceRequest.mock.calls[0][0].params.toString()).toBe(expectedParams)
})
})
However for some reason the middy middleware is still being invoked, which i clearly dont want and i have tried to mock away... what am i doing wrong?
You need to mock middy instead, to make it becomes a useless function. That function recipe a function as a parameter and return that parameter.
import thirdPartyService from '../src/thirdPartyService'
jest.mock('#middy/core', () => {
return (handler) => {
return {
use: jest.fn().mockReturnValue(handler), // ...use(ssm()) will return handler function
}
}
})
describe('thirdPartyService()', () => {
beforeEach(() => {
jest.spyOn(helpers, 'makeRequest') // spy on helpers unit
})
describe('makeThirdPartyServiceRequest', () => {
it('should make a request with correct parameters', async () => {
// Given
const url = `https://someurltoathirdpartyservice`
const params = 'any params'
const apiResponse = 'any response'
mocked(helpers.makeRequest).mockResolvedValue(apiResponse)
// When
const actual = await thirdPartyService.makeThirdPartyServiceRequest(params)
// Then
expect(actual).toBe(apiResponse)
expect(helpers.makeRequest).toHaveBeenCalledWith(
url,
{
method: 'POST',
body: params
}
)
})
})
})
hoangdv answer is also valid, but i will answer as well how i continued.
if you completely want to mock middy you mock like following:
jest.mock('#middy/core', () => {
return (handler) => {
return {
use: jest.fn().mockImplementation(() => {
// ...use(ssm()) will return handler function
return {
before: jest.fn().mockReturnValue(handler)
}
})
}
}
})
However if you dont want to completely mock middy, you can instead mock the async getInternal function from middy/util called in before like this:
jest.doMock('#middy/util', () => ({
...(jest.requireActual('#middy/util') as {}),
getInternal: jest.fn()
}))
import { getInternal } from '#middy/util'
and then in the test
describe('thirdPartyService()', () => {
beforeEach(() => {
jest.spyOn(helpers, 'makeRequest') // spy on helpers unit
})
describe('makeThirdPartyServiceRequest', () => {
it('should make a request with correct parameters', async () => {
// Given
const url = `https://someurltoathirdpartyservice`
const params = 'any params'
const apiResponse = 'any response'
mocked(getInternal).mockResolvedValue({
twilioSecrets: { accountSid: 'someSID', serviceId:
'someServiceID', token: 'someToken' }
})
mocked(helpers.makeRequest).mockResolvedValue(apiResponse)
// When
const actual = await thirdPartyService.makeThirdPartyServiceRequest(params)
// Then
expect(actual).toBe(apiResponse)
expect(helpers.makeRequest).toHaveBeenCalledWith(
url,
{
method: 'POST',
body: params
}
)
})
})
})
this will mock the async part of middy.

How to mock SQL Server connection pool using Jest?

I am trying to write a jest unit test for a function that utilizes mssql.
import * as sql from "mssql";
let pool: sql.ConnectionPool = null;
export async function handler() {
if (pool === null) {
try {
pool = new sql.ConnectionPool("");
await pool
.request()
.input("SomeInput", sql.NVarChar(255), "input")
.execute("SomeStoredProcedure");
} catch (err) {
console.error(err);
}
}
}
What would be the simplest way to mock the sql methods and assert they have been called?
import { handler } from "../src/main";
describe("test handler", () => {
it("should succeed", async () => {
const requestFn = jest.fn();
const executeFn = jest.fn();
const inputFn = jest.fn();
// Mock mssql connection pool with above functions
// *????????*
await handler();
// Expect the functions have been called
expect(requestFn).toHaveBeenCalled();
expect(executeFn).toHaveBeenCalled();
expect(inputFn).toHaveBeenCalled();
});
});
Sandbox
You can mock the mssql package by using an jest ES6 Class Mocks. You can achive that by using:
const mockExecute = jest.fn();
const mockInput = jest.fn(() => ({ execute: mockExecute }));
const mockRequest = jest.fn(() => ({ input: mockInput }));
jest.mock('mssql', () => ({
ConnectionPool: jest.fn(() => ({
request: mockRequest
})),
NVarChar: jest.fn()
}));
Have a look at the Stackblitz project and run jest in the terminal. You should see that the tests are passing.
You need to mock the return value of each function in the chain. You can do this using jest.fn().mockImplementation(implementation)
Expanding you example to use this give us the following
import { handler } from "../src/main";
let pool;
describe("test handler", () => {
it("should succeed", async () => {
const requestFn = jest.fn();
const executeFn = jest.fn();
const inputFn = jest.fn();
pool = {
request: requestFn,
execute: executeFn,
inputFn: inputFn,
};
requestFn.mockImplementation(() => pool);
executeFn.mockImplementation(() => pool);
inputFn.mockImplementation(() => pool);
await handler();
// Expect the functions have been called
expect(requestFn).toHaveBeenCalled();
expect(executeFn).toHaveBeenCalled();
expect(inputFn).toHaveBeenCalled();
});
});

Test component calling API inside useEffect

I want to test the api call and data returned which should be displayed inside my functional component. I have a component that calls an API when it is first loaded and when certain things change i.e when typing.
I have a useEffect calling the API like so:
useEffect(() => {
const query = queryString;
const apiRequest = async () => {
try {
setIsFetching(true);
const response = await getData(query, page);
setData(response.data);
} catch (error) {
// Do some error
console.log('error');
} finally {
setIsFetching(false);
}
};
apiRequest();
}, [queryString, page]);
getData is an axios function like so:
let getDataRequest;
const getData = (searchQuery = null, page = PAGE, size = PAGE_SIZE) => {
if (getDataRequest) getDataRequest.cancel();
getDataRequest = axios.CancelToken.source();
return axios.get('url_to_api', {
params: {
page,
searchQuery,
size,
},
cancelToken: getDataRequest.token,
});
};
When trying to test this component I am running into the error When testing, code that causes React state updates should be wrapped into act(...):
I have been trying to follow first link also second link third link and many more pages but still no luck.
Here is one version of the test I am still seeing the error:
it('should render data', async () => {
let container;
act(async () => {
await getData.mockResolvedValueOnce({
data: { things: data, meta: { current_page: 1 } },
});
container = render(<Component />);
});
await waitFor(() => container.queryByText('Text I would expect after api call'));
})
I have also tried mocking the function in my test file like so:
import { getData } from './dataAccess';
const mockedData = { data: { things: data } };
jest.mock('./api', () => ({
getData: jest
.fn()
.mockImplementation(() => new Promise(resolve => resolve(mockedData))),
}));
I am using import { act } from 'react-dom/test-utils'; with enzyme and jest. I also am using '#testing-library/react';

Why can't I load the API?

I'm going to call the movie API using the redux in the react application.
During the process of calling the movie API using the redux-thunk,
An error occurs while calling the callAPI function on the lib/THMb path.
//movie_project/src/lib/THMb.js
import axios from 'axios';
const key = "xxxxxxx";
const url = `https://api.themoviedb.org/3/movie/now_playing?api_key=${key}&language=ko&page=1&region=KR`;
export const callAPI = async () =>{
await axios.get(`${url}`);
}
import { handleActions } from "redux-actions";
import axios from "axios";
import * as movieAPI from '../lib/THMb';
// action types
const GET_MOVIES = 'movie/GET_MOVIES';
const GET_MOVIES_SUCCESS = 'movie/GET_MOVIES_SUCCESS';
const GET_MOVIES_FAILURE = 'movie/GET_MOVIES_FAILURE';
export const getMovies = () => async dispatch => {
dispatch({ type: GET_MOVIES });
try{
const res = await movieAPI.callAPI(); // failed
dispatch({
type: GET_MOVIES_SUCCESS, // 요청 성공
payload: res.data.results, // API 요청 결과 값
})
}catch(e){
dispatch({
type: GET_MOVIES_FAILURE, // 요청 실패
payload: e,
error: true
})
throw e;
}
}
const initialState ={
movieList : [],
error: null
}
const movie = handleActions(
{
[GET_MOVIES]: state => ({
...state,
// loading..
}),
[GET_MOVIES_SUCCESS]: (state, action) => ({
...state,
movieList: action.payload,
}),
[GET_MOVIES_FAILURE]: (state, action) => ({
...state,
// loading...
})
},
initialState
)
export default movie;
enter image description here
However, no error occurs when calling url from within the getMovies function.
export const getMovies = () => async dispatch => {
dispatch({ type: GET_MOVIES }); // 요청의 시작을 알림.
try{
//const res = await movieAPI.callAPI(); // failed
// success
const res = await axios.get(`https://api.themoviedb.org/3/movie/now_playing?api_key=xxxxx&language=ko&page=1&region=KR`);
dispatch({
type: GET_MOVIES_SUCCESS, // 요청 성공
payload: res.data.results, // API 요청 결과 값
})
Why do errors occur in the first case???
That's because in the first case you are not returning anything. You should try this:
export const callAPI = async () => {
let res = await axios.get(`${url}`);
return res;
}
Hope this works for you.
the error occurs in callAPI function, not in getMovies because in payload you are assuming res variable to fetch the data from it and you successfully get it in getMovies function but not in callAPI.
because you did not return anything from callAPI method that's why res variable is null and it throws the error.
just replace you callAPI function with the below code.
export const callAPI = async () =>{
const res await axios.get(`${url}`);
return res
}
hopefully, it will work just give it a try

how to assert an async action dispatched inside an async action in redux-thunk

I'm trying to assert an async action is dispatched by an async action like so :
// synchronous actions
export const getObjects = () => ({ type: 'GET_OBJECTS' });
export const addObject = object => ({ type: 'ADD_OBJECT', object });
// an async action
export const getObjectsAsync = () =>
dispatch =>
axios.get(URL).then((data) => {
dispatch(getObjects());
});
// another async action that dispatches the previous async action
export const postObjectAsync = newObject =>
dispatch =>
axios.post(URL, newObject)
.then(() => { dispatch(addObject(newObject)); })
.then(() => { dispatch(getObjectAsync()); });
// the test
describe('postObjectAsync()', () => {
it('should return ADD_OBJECT and GET_OBJECT actions', () => {
const object = mockedObject;
const store = mockedStore;
const expectedActions = [
{ type: 'ADD_OBJECT', object },
{ type: 'GET_OBJECTS', objects }, // I expected to see this object on store.getActions()[1]
];
return store.dispatch(postObjectAsync(object))
.then(() => {
store.getActions().should.deep.equal(expectedActions);
// AssertionError: expected [ Array(1) ] to deeply equal [ Array(2) ]
});
});
});
I expected store.getActions() to contain an array with both the GET_OBJECTS and ADD_OBJECT actions inside it but it only contains the ADD_OBJECT action
Can anybody weigh in?
Figured it out, the problem is not in the test,
// another async action that dispatches the previous async action
export const postObjectAsync = newObject =>
dispatch =>
axios.post(URL, newObject)
.then(() => { dispatch(addObject(newObject)); })
.then(() => { dispatch(getObjectAsync()); });
should be
// another async action that dispatches the previous async action
export const postObjectAsync = newObject =>
dispatch =>
axios.post(URL, newObject)
.then(() => {
dispatch(addObject(newObject));
return dispatch(getObjectAsync());
});
I just realized shouldn't use .then() on a synchronous function.
This post helped : How to handle two consecutive and dependant async calls in redux

Categories

Resources