Nock not working with axios get at actions async test - reactjs

I am trying to test my async actions at redux but I am not getting it.
I am using nock and axios, so I am trying to receive a responde data from axios get to test my actions:
describe('Async Actions', () => {
afterEach(() => {
nock.cleanAll();
});
it('should load transmissors', (done) => {
const localStorage = {
token: 'a9sd8f9asdfiasdf'
};
nock('https://tenant.contactto.care')
.get('/api/clients/1/transmissors/', {
reqheaders: { 'Authorization': "Token " + localStorage.token }
})
.reply(200, { data: [
{
"id": 12,
"zone": "013",
"client": 1,
"description": "pingente",
"identifier": "",
"general_info": ""
},
{
"id": 15,
"zone": "034",
"client": 1,
"description": "colar",
"identifier": "",
"general_info": ""
}
]});
axios.get(`/api/clients/1/transmissors/`, {
headers: { 'Authorization': "Token " + localStorage.token },
}).then(transmissors => {
console.log(transmissors);
}).catch(error => {
throw(error);
})
done();
});
});
and here is my action:
export function loadTransmissors(clientId){
return function(dispatch){
axios.get(`/api/clients/${clientId}/transmissors/`, {
headers: { 'Authorization': "Token " + localStorage.token },
}).then(transmissors => {
dispatch(loadTransmissorsSuccess(transmissors.data, clientId));
}).catch(error => {
throw(error);
})
}
}
But I receiving this error at console.log:
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 3): SyntaxError
I found this answer from Dan Abramov:
How to unit test async Redux actions to mock ajax response
https://github.com/reactjs/redux/issues/1716
Does anyone know how to make a test with redux-thunk.withExtraArgument?
Thanks in advance.

I solved my problem injecting axios via argument at redux thunk
https://github.com/gaearon/redux-thunk#injecting-a-custom-argument
So I changed my redux thunk at my store:
applyMiddleware(thunk)
for
applyMiddleware(thunk.withExtraArgument({ axios }))
So I updated my async return functions at actions
From:
return (dispatch) => {
...
}
To:
return (dispatch, getState, { axios }) => {
...
}
at my actions.test I mocked an api with promises:
const axios = {
get: (url,params) => Promise.resolve({data: transmissors})
}
injected at redux thunk:
const middleware = [thunk.withExtraArgument({axios})];
const mockStore = configureMockStore(middleware);
function asyncActions () {
return dispatch => {
return Promise.resolve()
.then(() => dispatch(transmissorsActions.loadTransmissors(1)))
}
}
and I used the function asyncActions to test my actions at my store:
it('should load transmissors', (done) => {
const expectedAction = { type: types.LOAD_TRANSMISSORS_SUCCESS, transmissors, clientId};
const store = mockStore({transmissors: [], expectedAction});
store.dispatch(asyncActions()).then(() => {
const action = store.getActions();
expect(action[0].type).equal(types.LOAD_TRANSMISSORS_SUCCESS);
expect(action[0].transmissors).eql(transmissors);
expect(action[0].clientId).equal(1);
});
done();
});
You can have more info about redux-mock-store with this sample:
https://github.com/arnaudbenard/redux-mock-store/blob/master/test/index.js

Related

How to write test case for axios interceptor using react testing library

Good day to you, I want to write unit test case and coverage for my axios interceptor file in react. I tried multiple documentation on web but none is working as expected. Below is the interceptor file code (interceptor.js)
import store from '../store';
axios.interceptors.request.use(
(config) => {
const {
auth: { data: { parsedToken: { accessToken: { accessToken, expiresAt } } } },
} = store.getState();
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
if (expiresAt < new Date()/1000) {
throw new axios.Cancel('Session Expired');
}
return config;
},
);
axios.interceptors.response.use(
(response) => response.data,
(error) => {
if (error instanceof axios.CanceledError && error.message === 'Session Expired') {
store.dispatch({
type: 'auth/setSessionTimeout',
});
}
return Promise.reject((error && error.response && error.response.data) || 'Something went wrong.');
}
);
And below is unit test case for interceptor using #testing-library with file name interceptor.test.js
import axios from "axios";
import '../interceptor';
import {screen, render } from "#testing-library/react";
jest.mock('axios', () => {
return {
interceptors: {
request: { use: jest.fn(), eject: jest.fn() },
response: { use: jest.fn(), eject: jest.fn() },
},
};
});
describe('Axios interceptor', () => {
it('Axios request interceptor use', async () => {
axios.interceptors.request.use.mockImplementation((callback) => {
screen.log(callback)
const result = axios.interceptors.request.handlers[0].fulfilled({ headers: {} });
screen.debug(result);
});
});
});
below is test coverage for interceptor. I want it to be at least 90%.
Any help and suggestion will be appreciated. Thanks in advance.

axios returns "fullfilled" promise

Can someone please tell me why my Axios service function returns this:
The object looks fine in the service function.
Promise {<fulfilled>: {…}}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Object
Here is the call:
useEffect(() => {
setData(playlistService.getPlaylists);
}, []);
console.log(data);
And the function:
const config = {
headers: {
'Content-Type': 'application/json',
},
withCredentials: true,
};
const getPlaylists = async () => {
try {
const res = await axios.get(`${API_SONGS_URL}/`, config);
console.log('RES ', res);
return res;
} catch (error) {
if (error.response) {
return error.response.data;
}
}
};
this could work
useEffect(() => {
const fetchPlayListAsync = async () => {
const response = await playlistService.getPlaylists();
setData(response)
}
fetchPlayListAsync()
}, []);
you can add appropriate checks of fetched data

fetchMock function makes actual API calls instead of mocking the requests

I am trying to test my signupAction shown below.
import axios from 'axios';
import actionTypes from '../action_types';
import { apiRequest } from '../common_dispatch';
export const signupAction = (user) => async (dispatch) => {
dispatch(apiRequest(true));
await axios
.post(`${process.env.REACT_APP_API_URL}/users`, { ...user }, {
headers: { 'Content-Type': 'application/json' },
})
.then((response) => {
dispatch(
{
type: actionTypes.REGISTER_SUCCESS,
payload: response.data.user,
},
);
dispatch(apiRequest(false));
})
.catch((error) => {
let errors = 'ERROR';
if (error.message === 'Network Error') {
errors = error.message;
} else {
errors = error.response.data.errors;
console.log(error);
}
dispatch(
{
type: actionTypes.REGISTER_FAIL,
payload: errors,
},
);
dispatch(apiRequest(false));
});
};
I figured I could mock the API call above using the fetchMock library. The problem is fetchMock makes actual calls hence the test passes in the first intance but fails when I run it the second time because the user I am trying to sign up already exists. my test is as shown below.
mport configureMockStore from 'redux-mock-store';
import * as actions from './signup.action';
import mocks from './mocks';
import thunk from 'redux-thunk';
import fetchMock from 'fetch-mock';
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
describe('signUp actions', () => {
afterEach(() => {
fetchMock.resetMocks();
console.log('yess bro am called')
})
it('dispatches create REGISTER_FAIL when signup has been done', async () => {
fetchMock.postOnce('/users', { ...mocks.user }, {
headers: { 'Content-Type': 'application/json' },
});
const expectedActions = [
{ type: 'API_REQUEST', payload: true },
{ type: 'REGISTER_FAIL', payload: { email: "Email karanilarrygmail.com is not a valid email" } },
{ type: 'API_REQUEST', payload: false },
]
const store = mockStore(mocks.user);
return store.dispatch(actions.signupAction(mocks.user)).then(() => {
expect(store.getActions()).toEqual(expectedActions)
})
});
mocks.user is an object containing the user signup data.
What am I doing wrong

Branch coverage zero percent in jest

I have written some test cases and everything seems fine except the following one. I am getting zero branch cover for one file. I have googled couple of blog and I came to understand if the statement cab be executed in multiple scenario that call branch coverage. But I don't find my code can be executed in multiple way.
request.js
import axios from 'axios';
export default async (request, httpService = axios) => {
const {
method, url, data, headers,
} = request;
return httpService.request({
method,
url,
headers: Object.assign({}, headers),
data,
});
};
reqeust.test.js
describe('requestServie', () => {
it('should have a valid request object', async () => {
const requestObj = {
method: 'POST',
url: 'http://mock.url',
data: {},
};
const mockRequest = jest.fn(() => Promise.resolve({}));
const httpService = {
request: mockRequest,
};
await request(requestObj, httpService);
expect(mockRequest).toHaveBeenCalledWith({
method: requestObj.method,
url: requestObj.url,
headers: {},
data: requestObj.data,
});
});
it('should return a valid response (empty)', async () => {
const response = {
data: {
},
status: 200,
statusText: 'OK',
headers: {},
config: {},
request: {},
};
const mockRequest = jest.fn(() => Promise.resolve(response));
const httpService = {
request: mockRequest,
};
const res = await request({ url: 'http://mock.url' }, httpService);
expect(res).not.toBe(null);
expect(res).toMatchObject(
{
status: response.status,
},
);
});
});
Edit
rquest.js
export default async (request, httpService = axios) => {
const {
method, url, data, headers,
} = request;
return httpService.request({
method,
url,
headers: Object.assign({}, headers),
data,
}).then(successResponse, (error) => {
throwHttpError(error);
});
};
request.test.js
import HttpError from 'standard-http-error';
import axios from 'axios';
import request, { successResponse, throwHttpError } from './requestService';
describe('requestService', () => {
jest.mock('axios', () => ({
request: jest.fn(() => Promise.resolve({})),
}));
describe('successResponse', () => {
const mockRes = {
status: 9001,
data: {
stuff: 'stuff',
},
};
it('should returns an object with only status and data properties', () => {
const responseKeys = Object.keys(successResponse(mockRes));
expect(responseKeys).toMatchObject(['status', 'data']);
expect(responseKeys.length).toBe(2);
});
it('should map the status of the reponse to the status property', () => {
expect(successResponse(mockRes).status)
.toBe(mockRes.status);
});
it('should map the data of the reponse to the data property', () => {
expect(successResponse(mockRes).data)
.toMatchObject(mockRes.data);
});
it('should have a valid request object', async () => {
const requestObj = {
method: 'POST',
url: 'http://mock.url',
data: {},
headers: {},
};
const mockRequest = jest.fn(() => Promise.resolve({}));
const httpService = {
request: mockRequest,
};
await request(requestObj, httpService);
expect(mockRequest).toHaveBeenCalledWith({
method: requestObj.method,
url: requestObj.url,
headers: {},
data: requestObj.data,
});
});
});
describe('httpThrowError', () => {
const mockErr = {
response: {
status: 9001,
statusText: 'error message goes here',
},
};
it('should map the status of the reponse to the error.status property', () => {
try {
throwHttpError(mockErr);
} catch (e) {
expect(e).not.toBe(null);
expect(e.status).toBe(mockErr.response.status);
expect(e.message).toBe(mockErr.response.statusText);
}
});
it('should map the data of the reponse to the error.data property', () => {
const mockErrWithData = Object.assign({}, mockErr);
mockErrWithData.response.data = {};
try {
throwHttpError(mockErrWithData);
} catch (e) {
expect(e).not.toBe(null);
expect(e.data).toBe(mockErrWithData.response.data);
}
});
});
describe('request', () => {
const testCases = [
['should return error response on server error', 500],
['should return error response on bad request', 400],
['should return error response on unauthorised', 401],
];
testCases.forEach(([testName, errorStatus]) => {
it(testName, async () => {
const errorResponse = {
response: {
status: errorStatus,
},
};
const mockRequest = jest.fn(() => Promise.reject(errorResponse));
const httpService = {
request: mockRequest,
};
try {
await request({ url: 'http://mock.url' }, httpService);
throw new Error('Expected an exception, but none was thrown');
} catch (err) {
expect(err).not.toBe(null);
expect(err).toMatchObject(
new HttpError(errorResponse.response.status,
errorResponse.response.statusText),
);
}
});
});
it('should return an valid response (empty)', async () => {
const response = {
data: {
meta: {},
results: [],
},
status: 200,
statusText: 'OK',
headers: {},
config: {},
request: {},
};
const mockRequest = jest.fn(() => Promise.resolve(response));
const httpService = {
request: mockRequest,
};
const res = await request({ url: 'http://mock.url' }, httpService);
expect(res).not.toBe(null);
expect(res).toMatchObject(
{
status: response.status,
data: response.data,
},
);
});
it('should use axios by default', async () => {
const req = { url: 'http://mock.url', method: 'get' };
await request(req);
expect(axios.request).toHaveBeenCalled();
});
});
});
Error
Updated 15/Nov/18
"jest": "^23.6.0",
import HttpError from 'standard-http-error';
import axios from 'axios';
import request, { successResponse, throwHttpError } from './requestService';
jest.mock('axios', () => ({
request: jest.fn(),
}));
Error
To see what is not covered you can go to coverage/Iconv-report and open index.html. Those are created once you run jest with --coverage option.
It looks like uncovered branch is: httpService = axios. So you need to check if default axios is used.
To cover that you may run request without httpService argument - you can mock axios globally for that, i.e.:
import axios from 'axios';
// note that mock goes before any describe block
jest.mock('axios', () => {
return {
request: jest.fn(() => Promise.resolve({})),
}
});
describe('requestService', () => {
// ... your other tests
it('should use axios by default', async () => {
const opts = { url: 'http://mock.url', method: 'get' };
const res = await request(opts);
expect(axios.request).toHaveBeenCalled();
});
});
Note that jest.mock have some buggy behavior when running inside a spec.

Put error response interceptor on redux-axios-middleware

I have a problem with https://github.com/svrcekmichal/redux-axios-middleware.
I want to set the interceptor response (error). But can't successfully set it up.
Here is my code:
function interceptorResponse({ dispatch, getState, getAction }, response) {
console.log(response);
}
export const client = axios.create({
baseURL: API_URL,
headers: {
Accept: 'application/json',
},
});
export const clientOptions = {
interceptors: {
request: [interceptorRequest],
response: [interceptorResponse],
},
};
the console.log(response) only respond if the response is 200. How can I set it to accept an error response?
I've tried set it like this
function interceptorResponse({ dispatch, getState, getAction }) {
return response => response.data, (error) => {
const meta = error.response.data.meta;
const { code, status } = meta;
console.log(meta);
};
}
but still never show anything.
Any soluion?
Here is an example usage with ES6 :
import axios from 'axios'
import axiosMiddleware from 'redux-axios-middleware'
const options = {
// not required, but use-full configuration option
returnRejectedPromiseOnError: true,
interceptors: {
request: [
({ getState, dispatch }, config) => {
// Request interception
return config
}
],
response: [
{
success: ({ dispatch }, response) => {
// Response interception
return response
},
error: ({ dispatch }, error) => {
// Response Error Interception
return Promise.reject(error)
}
}
]
}
}
export default axiosMiddleware(axios, options)
Note that the created middleware should be passed to createStore()

Resources