Intercept axios request when testing - node.js

I am testing a software and I would like to verify that the request being sent to an API has the correct data. This particular method creates a request with certain data, headers etc. and then makes the request to an external API via axios. Example code:
myFunction() {
const data = {
example: 'my data'
};
const headers = {
'content-type': 'application/json'
}
const request = {
method: 'POST',
baseUrl: 'http://myawesome-site.com',
url: '/api/path',
headers,
data,
}
return axios(request)
.then(res => ...do something)
.catch(err => ...do something else)
}
I would like to know if there is a way using chai or sinon to intercept the axios call and get access only to the "request" object to just verify the data being sent, I don't care about the response.

Here is an unit test solution, you should use sinon.stub:
When you want to prevent a specific method from being called directly (possibly because it triggers undesired behavior, such as a XMLHttpRequest or similar
E.g.
index.ts:
import axios, { AxiosRequestConfig } from 'axios';
exports.myFunction = function myFunction() {
const data = {
example: 'my data'
};
const headers = {
'content-type': 'application/json'
};
const request: AxiosRequestConfig = {
method: 'POST',
baseURL: 'http://myawesome-site.com',
url: '/api/path',
headers,
data
};
return exports
.axios(request)
.then(res => console.log('do something'))
.catch(err => console.log('do something else'));
};
exports.axios = axios;
index.spec.ts:
const mod = require('./');
import sinon from 'sinon';
import { expect } from 'chai';
describe('myFunction', () => {
afterEach(() => {
sinon.restore();
});
it('should send request', async () => {
const mResponse = { status: 200 };
const axiosStub = sinon.stub(mod, 'axios').resolves(mResponse);
const logSpy = sinon.spy(console, 'log');
await mod.myFunction();
expect(
axiosStub.calledWith({
method: 'POST',
baseURL: 'http://myawesome-site.com',
url: '/api/path',
headers: {
'content-type': 'application/json'
},
data: {
example: 'my data'
}
})
).to.be.true;
expect(logSpy.calledWith('do something')).to.be.true;
});
it('should handle request error', async () => {
const mError = new Error('network error');
const axiosStub = sinon.stub(mod, 'axios').rejects(mError);
const logSpy = sinon.spy(console, 'log');
await mod.myFunction();
expect(
axiosStub.calledWith({
method: 'POST',
baseURL: 'http://myawesome-site.com',
url: '/api/path',
headers: {
'content-type': 'application/json'
},
data: {
example: 'my data'
}
})
).to.be.true;
expect(logSpy.calledWith('do something else')).to.be.true;
});
});
Unit test result with 100% coverage:
myFunction
do something
✓ should send request
do something else
✓ should handle request error
2 passing (14ms)
---------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.spec.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/57807855

Related

async node js jest unit tests

This is my nodejs code and I want to unit tests using jest and mocking the request function.
I am new to nodeJS, attached the mock function and the unit tests as well.
Please can someone add the right jest unit test code for below function.
async function getSomeTask(res) {
request.get({ url: someTaskUrl }, (response, body) => {
if (response.statusCode === 200) {
res.writeHead(200, { 'content-type': 'application/json' });
res.write(body);
res.end();
return;
}
const errorMessage = 'Failed';
res.status(400).json({ error: errorMessage });
});
}
// mock function in __mocks__
function get({url}) {
return this;
}
const handler = require("../../handler");
const request = require("../__mocks__/request");
jest.mock("request");
describe("testHandler", () => {
it("test", async () => {
const response1 = {
statusCode: 200,
body: { Test: "test" },
};
const url = "http://example.com";
jest.fn().mockImplementation({ url }, (response, body) => {
return Promise.resolve(response1);
});
const req = {
body: { requestId: 1111 },
capabilitiesCheckUrl: "http://capabilities-test",
};
const res = {};
const resp = handler.getSomeTask(res);
console.log(resp);
});
});
If possible give me an explanation as well.
You don't need to mock the entire request module by using jest.mock('request') and creating mocks inside __mocks__ directory. You can use jest.spyOn(request, 'get') to only mock request.get method.
handler.js:
const request = require('request');
const someTaskUrl = 'http://localhost:3000/api'
async function getSomeTask(res) {
request.get({ url: someTaskUrl }, (response, body) => {
if (response.statusCode === 200) {
res.writeHead(200, { 'content-type': 'application/json' });
res.write(body);
res.end();
return;
}
const errorMessage = 'Failed';
res.status(400).json({ error: errorMessage });
});
}
module.exports = {
getSomeTask,
};
handler.test.js:
const handler = require('./handler');
const request = require('request');
describe('73754637', () => {
test('should get some task success', () => {
jest.spyOn(request, 'get').mockImplementation((options, callback) => {
const mResponse = { statusCode: 200 };
const mBody = { Test: 'test' };
callback(mResponse, mBody);
});
const mRes = {
writeHead: jest.fn(),
write: jest.fn(),
end: jest.fn(),
};
handler.getSomeTask(mRes);
expect(request.get).toBeCalledWith({ url: 'http://localhost:3000/api' }, expect.any(Function));
expect(mRes.writeHead).toBeCalledWith(200, { 'content-type': 'application/json' });
expect(mRes.write).toBeCalledWith({ Test: 'test' });
expect(mRes.end).toBeCalledTimes(1);
});
});
Test result:
PASS stackoverflow/73754637/handler.test.js (10.283 s)
73754637
✓ should get some task success (5 ms)
------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files | 81.82 | 50 | 100 | 81.82 |
handler.js | 81.82 | 50 | 100 | 81.82 | 12-13
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 10.807 s, estimated 11 s

How do I mock an async function that makes network request using axios?

I want to unit test a function below that calls an endpoint using axios in my node.js server.
const callValidateCookieApi = async (cookie) => {
try {
const config = {
method: 'post',
url: process.env.API_COOKIE_VALIDATION,
headers: {
Cookie: cookie
}
}
return await axios(config)
} catch (error) {
console.log(error.message)
return error
}
}
How do I write unit test cases by mocking the axios call that is inside the function?
In order to stub axios function, you need an extra package named proxyquire. For more info, see How to use Link Seams with CommonJS
Unit test solution:
index.js:
const axios = require('axios');
const callValidateCookieApi = async (cookie) => {
try {
const config = {
method: 'post',
url: process.env.API_COOKIE_VALIDATION,
headers: {
Cookie: cookie,
},
};
return await axios(config);
} catch (error) {
console.log(error.message);
return error;
}
};
module.exports = { callValidateCookieApi };
index.test.js:
const proxyquire = require('proxyquire');
const sinon = require('sinon');
const { expect } = require('chai');
describe('64374809', () => {
it('should pass', async () => {
const axiosStub = sinon.stub().resolves('fake data');
const { callValidateCookieApi } = proxyquire('./', {
axios: axiosStub,
});
const actual = await callValidateCookieApi('sessionId');
expect(actual).to.be.eql('fake data');
sinon.assert.calledWithExactly(axiosStub, {
method: 'post',
url: undefined,
headers: {
Cookie: 'sessionId',
},
});
});
it('should handle error', async () => {
const mErr = new Error('network');
const axiosStub = sinon.stub().rejects(mErr);
const { callValidateCookieApi } = proxyquire('./', {
axios: axiosStub,
});
const actual = await callValidateCookieApi('sessionId');
expect(actual).to.be.eql(mErr);
sinon.assert.calledWithExactly(axiosStub, {
method: 'post',
url: undefined,
headers: {
Cookie: 'sessionId',
},
});
});
});
unit test result:
64374809
✓ should pass (84ms)
network
✓ should handle error
2 passing (103ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------

Jest + mockImplementation + Number of calls: 0

I am using Jest as testing module. Jest spy function is not called, it says Number of calls: 0.
a.test.js
const request = require('request-promise');
const moduleA = require('./a');
test('Update function', async () => {
const postSpy = jest.spyOn(request, 'post').mockImplementation((input) => input);
await moduleA();
expect(postSpy).toBeCalledWith({
method: 'POST',
uri:"fsdsfd",
headers: {
'content-type': 'application/json'
},
body: {
A:A,
B:B
},
json: true
});
});
a.js
const request = require('request-promise');
module.exports = async () => {
var options = {
method: 'POST',
uri: 'fsdsfd',
headers: {
'content-type': 'application/json',
},
body: {
A: A,
B: B,
},
json: true,
};
try {
const selectResult = await request(options);
return selectResult;
} catch (err) {
return err;
}
};
It throws error as expect(jest.fn()).toBeCalledWith(...expected) and Number of calls: 0. I think the mocked function is not called. I searched a lot regarding this but no hope. Any ideas??
EDIT
My Guess is that there is no method post in request-promise.
EDIT FOR REQUEST>POST()
request.post({
uri: 'fsdsfd',
headers: {
'content-type': 'application/json',
},
body: {
A: A,
B: B,
},
json: true,
}, function(err, resp){
if(err){
console.log(err)
} else {
console.log(resp);
return(resp)--------------->
}
});
ASYNC AWAIT EDIT
const ss = await request.post({
uri: 'fsdsfd',
headers: {
'content-type': 'application/json',
},
body: {
A: A,
B: B,
},
json: true,
});
return ss;
Here if you use this the arrow (-->) is not getting called so its not assign in test file so I can't check for result values.
You use request(...) instead of using request.post(...), so you need mock request function not request.post method.
E.g.
a.js:
const request = require('request-promise');
module.exports = async () => {
var options = {
uri: 'fsdsfd',
headers: {
'content-type': 'application/json',
},
body: {
A: 'A',
B: 'B',
},
json: true,
};
try {
const selectResult = await request.post(options);
return selectResult;
} catch (err) {
return err;
}
};
a.test.js:
const request = require('request-promise');
const moduleA = require('./a');
jest.mock('request-promise', () => {
return {
post: jest.fn(),
};
});
describe('59261385', () => {
afterEach(() => {
jest.resetAllMocks();
});
test('Update function', async () => {
request.post.mockResolvedValueOnce({ result: [] });
const actual = await moduleA();
expect(actual).toEqual({ result: [] });
expect(request.post).toBeCalledWith({
uri: 'fsdsfd',
headers: {
'content-type': 'application/json',
},
body: {
A: 'A',
B: 'B',
},
json: true,
});
});
});
Unit test result with coverage report:
PASS src/stackoverflow/59261385/a.test.js (11.669s)
59261385
✓ Update function (33ms)
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 87.5 | 100 | 100 | 83.33 | |
a.js | 87.5 | 100 | 100 | 83.33 | 20 |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 13.599s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59261385
Update:
You can use jest.spyOn too.
a.test.js:
const request = require('request-promise');
const moduleA = require('../a');
describe('59261385', () => {
afterEach(() => {
jest.restoreAllMocks();
});
test('Update function', async () => {
const postSpy = jest.spyOn(request, 'post').mockResolvedValueOnce({ result: [] });
const actual = await moduleA();
expect(actual).toEqual({ result: [] });
expect(postSpy).toBeCalledWith({
uri: 'fsdsfd',
headers: {
'content-type': 'application/json'
},
body: {
A: 'A',
B: 'B'
},
json: true
});
});
});
codesandbox: https://codesandbox.io/s/jestjs-jgye4

Write a unit test in mocha for the given method using mocks/stubs

I am pretty new to unit testing in node.js.
I have a function which looks something like this. I need to write a unit test for the function.
async refreshToken(req, res) {
const token = req.body.token || req.query.token;
const options = {method: 'POST', url: ''};
let resp;
try {
resp = await request(options);
} catch (e) {
console.error(e);
}
if (resp) {
const grant = {
another_token: {
token: resp.another_token,
},
expires_in: resp.expires_in
}
res.end(JSON.stringify(grant));
} else {
res.status(400).end("not authorized");
}
}
I am planning to use mocha framework with sinon and chai.
I tried a test using mocha, but could not figure out how to assert if the request(options) is called at least once.
describe('refreshToken', () => {
it('should take token from body', async () => {
const req = {
body: {
token: "123"
}
}
await auth.refreshToken(req, res);
request.should.have.been.calledOnceWithExactly(options);
})
I get:
TypeError: Cannot read property 'xxxxx' of undefined
I am having difficulty how to make this work with mock/stub.
Here is the unit test solution:
auth.ts:
import request from 'request-promise';
export async function refreshToken(req, res) {
const token = req.body.token || req.query.token;
const options = { method: 'POST', url: 'https://github.com/mrdulin' };
let resp;
try {
resp = await request(options);
} catch (e) {
console.error(e);
}
if (resp) {
const grant = {
another_token: {
token: resp.another_token
},
expires_in: resp.expires_in
};
res.end(JSON.stringify(grant));
} else {
res.status(400).end('not authorized');
}
}
auth.spec.ts:
import sinon from 'sinon';
import proxyquire from 'proxyquire';
import { expect } from 'chai';
describe('auth', () => {
describe('#refreshToken', () => {
it('should take token from body', async () => {
const mResponse = { another_token: '123', expires_in: '3600' };
const requestPromiseStub = sinon.stub().resolves(mResponse);
const auth = proxyquire('./auth', {
'request-promise': requestPromiseStub
});
const req = {
body: {
token: '123'
}
};
const res = { end: sinon.stub() };
await auth.refreshToken(req, res);
expect(requestPromiseStub.calledWith({ method: 'POST', url: 'https://github.com/mrdulin' })).to.be.true;
expect(
res.end.calledWith(
JSON.stringify({
another_token: {
token: mResponse.another_token
},
expires_in: mResponse.expires_in
})
)
).to.be.true;
});
it('should cause not authorized error', async () => {
const mError = new Error('network error');
const requestPromiseStub = sinon.stub().rejects(mError);
const auth = proxyquire('./auth', {
'request-promise': requestPromiseStub
});
const errorLogSpy = sinon.spy(console, 'error');
const req = {
body: {
token: '123'
}
};
const res = { status: sinon.stub().returnsThis(), end: sinon.stub() };
await auth.refreshToken(req, res);
expect(errorLogSpy.calledWith(mError)).to.be.true;
expect(res.status.calledWith(400)).to.be.true;
expect(res.status().end.calledWith('not authorized')).to.be.true;
});
});
});
Unit test result with coverage report:
auth
#refreshToken
✓ should take token from body (1262ms)
Error: network error
at /Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/57479631/auth.spec.ts:1:26462
at step (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/57479631/auth.spec.ts:1:23604)
at Object.next (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/57479631/auth.spec.ts:1:20644)
at /Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/57479631/auth.spec.ts:1:19788
at new Promise (<anonymous>)
at __awaiter (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/57479631/auth.spec.ts:1:18995)
at Context.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/src/stackoverflow/57479631/auth.spec.ts:1:26156)
at callFn (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runnable.js:387:21)
at Test.Runnable.run (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runnable.js:379:7)
at Runner.runTest (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:535:10)
at /Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:653:12
at next (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:447:14)
at /Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:457:7
at next (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:362:14)
at Immediate.<anonymous> (/Users/ldu020/workspace/github.com/mrdulin/mocha-chai-sinon-codelab/node_modules/mocha/lib/runner.js:425:5)
at runCallback (timers.js:705:18)
at tryOnImmediate (timers.js:676:5)
at processImmediate (timers.js:658:5)
✓ should cause not authorized error (88ms)
2 passing (1s)
--------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
--------------|----------|----------|----------|----------|-------------------|
All files | 100 | 75 | 100 | 100 | |
auth.spec.ts | 100 | 100 | 100 | 100 | |
auth.ts | 100 | 75 | 100 | 100 | 4 |
--------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/57479631

Jest mocking axios's request method giving error

I am trying to mock the axios's request method. But it throwing error
it('should execute axios request method once', async () => {
jest.mock('axios');
axios.request.mockImplementation(() =>
Promise.resolve({
data: {}
})
);
const requestObj = {
method: 'GET',
url: 'http://mock.url',
headers: {}
};
await request(requestObj);
expect(axios.request).toHaveBeenCalledTimes(1);
});
request.js
export default async (request, httpService = axios) => {
const { method, data, headers } = request;
let { url } = request;
const token = getLocalstorage('token');
if (token) {
headers.token = token;
}
if (method === 'GET') {
if (data) {
url += `?${serialize(data)}`;
}
}
return httpService
.request({
method,
url,
headers: Object.assign({}, headers),
...(method !== 'GET' && { data })
})
.then(successResponse, error => {
throwHttpError(error);
});
};
error
Here is the solution based on:
"jest": "^24.8.0",
"ts-jest": "^24.0.2",
"typescript": "^3.5.3"
"axios": "^0.19.0",
request.ts:
import axios from 'axios';
const serialize = data => data;
const getLocalstorage = key => key;
const successResponse = () => console.log('successResponse');
const throwHttpError = error => new Error(error);
export default async (request, httpService = axios) => {
const { method, data, headers } = request;
let { url } = request;
const token = getLocalstorage('token');
if (token) {
headers.token = token;
}
if (method === 'GET') {
if (data) {
url += `?${serialize(data)}`;
}
}
return httpService
.request({
method,
url,
headers: Object.assign({}, headers),
...(method !== 'GET' && { data })
})
.then(successResponse, error => {
throwHttpError(error);
});
};
Unit test, request.spec.ts
import request from './request';
import axios from 'axios';
jest.mock('axios');
describe('request', () => {
it('should execute axios request method once', async () => {
(axios.request as jest.Mock<any, any>).mockResolvedValueOnce({ data: 'mocked data' });
const requestObj = {
method: 'GET',
url: 'http://mock.url',
headers: {}
};
await request(requestObj);
expect(axios.request).toHaveBeenCalledTimes(1);
});
});
Unit test result:
PASS src/stackoverflow/57353897/request.spec.ts
request
✓ should execute axios request method once (13ms)
console.log src/stackoverflow/57353897/request.ts:4
successResponse
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.637s, estimated 3s

Resources