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
Related
I'm currently attempting to mock AWS SecretsManager for my unit testing with Jest, and everytime I'm hit with the ConfigError
My code is somewhat like this
//index.ts
import SM from "aws-sdk/clients/secretsmanager"
const secretManagerClient = new SM()
...
export const randomMethod = async (a: string, b: string) => {
let secret
const personalToken = {
SecretId: process.env.secretId,
}
secretManagerClient
.getSecretValue(personalToken, (err, data) => {
if (err) {
console.error(`[SECRETS MANAGER] Error fetching personal token : ${err}`)
} else if (data && data.SecretString) {
secret = data.SecretString
}
})
}
My mock goes like this :
//index.test.js
const mockGetSecretValue = jest.fn((SecretId) => {
switch (SecretId) {
case process.env.GITHUB_PERSONAL_TOKEN:
return {
SecretString: process.env.GITHUB_PERSONAL_TOKEN_VALUE,
}
default:
throw Error("secret not found")
}
})
jest.mock("aws-sdk/clients/secretsmanager", () => {
return jest.fn(() => {
return {
getSecretValue: jest.fn(({ SecretId }) => {
return mockGetSecretValue(SecretId)
}),
promise: jest.fn(),
}
})
})
However, I get this error thrown at me : ConfigError: Missing region in config, which I understand to some extent, however I don't understand why it occurs here in the mocking part...
Thanks in advance!
EDIT: Thanks to the 1st answer, I've managed to stop having this error. However, the getSecretValue() method is not returning the Secret value I want.
You should NOT use the callback of .getSecretValue() method with .promise() together. Just choose one of them. The error means you didn't mock the secretsmanager class correctly of aws-sdk.
E.g.
index.ts:
import SM from 'aws-sdk/clients/secretsmanager';
const secretManagerClient = new SM();
export const randomMethod = async () => {
const personalToken = {
SecretId: process.env.secretId || '',
};
try {
const data = await secretManagerClient.getSecretValue(personalToken).promise();
return data.SecretString;
} catch (err) {
console.error(`[SECRETS MANAGER] Error fetching personal token : ${err}`);
}
};
index.test.ts:
import { randomMethod } from '.';
import SM from 'aws-sdk/clients/secretsmanager';
import { mocked } from 'ts-jest/utils';
import { PromiseResult } from 'aws-sdk/lib/request';
jest.mock('aws-sdk/clients/secretsmanager', () => {
const mSecretManagerClient = {
getSecretValue: jest.fn().mockReturnThis(),
promise: jest.fn(),
};
return jest.fn(() => mSecretManagerClient);
});
describe('69977310', () => {
test('should get secret value', async () => {
process.env.secretId = 's1';
const mSecretManagerClient = mocked<InstanceType<typeof SM>>(new SM());
const mGetSecretValueRequest = mocked(mSecretManagerClient.getSecretValue());
mGetSecretValueRequest.promise.mockResolvedValue({
SecretString: JSON.stringify({ password: '123456' }),
} as PromiseResult<any, any>);
const actual = await randomMethod();
expect(actual).toEqual(JSON.stringify({ password: '123456' }));
expect(mSecretManagerClient.getSecretValue as jest.Mocked<any>).toBeCalledWith({ SecretId: 's1' });
});
test('should throw error', async () => {
process.env.secretId = 's1';
const logSpy = jest.spyOn(console, 'error').mockImplementation(() => 'suppress error log for testing');
const mSecretManagerClient = mocked<InstanceType<typeof SM>>(new SM());
const mGetSecretValueRequest = mocked(mSecretManagerClient.getSecretValue());
const mError = new Error('network');
mGetSecretValueRequest.promise.mockRejectedValue(mError);
await randomMethod();
expect(logSpy).toBeCalledWith(`[SECRETS MANAGER] Error fetching personal token : ${mError}`);
expect(mSecretManagerClient.getSecretValue as jest.Mocked<any>).toBeCalledWith({ SecretId: 's1' });
});
});
test result:
PASS examples/69977310/index.test.ts (7.722 s)
69977310
✓ should get secret value (4 ms)
✓ should throw error (1 ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 50 | 100 | 100 |
index.ts | 100 | 50 | 100 | 100 | 6
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 8.282 s, estimated 10 s
package versions:
"aws-sdk": "^2.875.0",
"typescript": "^4.1.2",
"jest": "^26.6.3",
I've overlooked the fact that I was using a callback in order to bypass the promise().
The following is the correct code:
const mockGetSecretValue = jest.fn((SecretId, callback) => {
console.log("secretId", SecretId)
switch (SecretId) {
case process.env.GITHUB_PERSONAL_TOKEN:
const data = {
SecretString: process.env.GITHUB_PERSONAL_TOKEN_VALUE,
}
callback(null, data)
break;
default:
const err = new Error("secret not found")
throw err
}
})
jest.mock("aws-sdk/clients/secretsmanager", () => {
return jest.fn(() => {
return {
promise: jest.fn(),
getSecretValue: jest.fn(({ SecretId }, callback) => {
return mockGetSecretValue(SecretId, callback)
}),
}
})
})
Thanks again for your help #slideshowp2!
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 |
----------|---------|----------|---------|---------|-------------------
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
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
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