For some reason I am having a little trouble getting this simple test to run correctly with a similar set-up I have used multiple times before.
Perhaps a fresh pair of eyes could help me understand why my method generateReport is not being invoked and none of my stubs are being triggered with the intended arguments?
BYE nor GOOD are ever logged and the tests are returning the error: AssertError: expected stub to be called with arguments
My index file:
const errorHandler = require('./lib/handlers/error-handler')
const transformRequest = require('./lib/handlers/request-converter')
const convert = require('./lib/convert')
exports.generateReport = function generateReport(req, res) {
console.log('HELLO')
const objectToPopulateTemplate = transformRequest(req.body)
convert(objectToPopulateTemplate, function (e, data) {
if (e) {
console.log('BYE')
const error = errorHandler(e)
return res.send(error.httpCode).json(error)
}
console.log('GOOD')
res
.set('Content-Type', 'application/pdf')
.set('Content-Disposition', `attachment; filename=velocity_report_${new Date()}.pdf`)
.set('Content-Length', data.length)
.status(200)
.end(data)
})
}
My test file:
const proxyquire = require('proxyquire')
const assert = require('assert')
const sinon = require('sinon')
const fakeData = require('./data/sample-request.json')
describe('report-exporter', () => {
describe('generateReport', () => {
const fakeError = new Error('Undefined is not a function')
let res, resSendStub, resStatusStub, resEndStub, resSetStub, resJsonStub, req, convertStub, generate
before(() => {
resSendStub = sinon.stub()
resJsonStub = sinon.stub()
resStatusStub = sinon.stub()
resEndStub = sinon.stub()
resSetStub = sinon.stub()
convertStub = sinon.stub()
res = {
send: function(errorCode) {
return resSendStub(errorCode)
},
json: function(object) {
return resJsonStub(object)
},
set: function(arg1, arg2) {
return resSetStub(arg1, arg2)
},
status: function(code) {
return resStatusStub(code)
},
end: function(data) {
return resEndStub(data)
}
}
req = {
body: {}
}
generate = proxyquire('./../index', {
'./lib/convert': function() {
return convertStub
}
})
})
it('Should return an error response', () => {
convertStub.throws(fakeError)
generate.generateReport(req, res)
sinon.assert.calledWith(resSendStub, '500')
})
})
})
It looks like you are proxyquireing your ./lib/convert incorrectly. Original convert is called with objectToPopulateTemplate and a callback function (e, data). And that's the callback who is responsible for error handling and sending a response.
The stubbed convert function though doesn't care about about the provided callback at all, so the callback never gets called.
Most likely what you want is to pass the parameters to convertStub and deal with them later:
'./lib/convert': function(objectToPopulateTemplate, cb) {
return convertStub(objectToPopulateTemplate, cb);
}
and then
it('Should return an error response', () => {
generate.generateReport(req, res);
const cb = convertStub.getCall(0).args[1];
// simulate `convert` to fail
cb(fakeError);
sinon.assert.calledWith(resSendStub, '500')
})
Here is the unit test solution:
index.js:
const errorHandler = require("./error-handler");
const transformRequest = require("./request-converter");
const convert = require("./convert");
exports.generateReport = function generateReport(req, res) {
console.log("HELLO");
const objectToPopulateTemplate = transformRequest(req.body);
convert(objectToPopulateTemplate, function(e, data) {
if (e) {
console.log("BYE");
const error = errorHandler(e);
return res.send(error.httpCode).json(error);
}
console.log("GOOD");
res
.set("Content-Type", "application/pdf")
.set(
"Content-Disposition",
`attachment; filename=velocity_report_${new Date()}.pdf`
)
.set("Content-Length", data.length)
.status(200)
.end(data);
});
};
error-handler.js:
module.exports = function(error) {
return error;
};
request-converter.js:
module.exports = function transformRequest(body) {
return body;
};
convert.js:
module.exports = function convert(body, callback) {
callback();
};
index.spec.js:
const sinon = require("sinon");
const proxyquire = require("proxyquire");
describe("report-exporter", () => {
describe("generateReport", () => {
afterEach(() => {
sinon.restore();
});
const fakeError = new Error("Undefined is not a function");
fakeError.httpCode = 500;
it("Should return an error response", () => {
const logSpy = sinon.spy(console, "log");
const mReq = { body: {} };
const mRes = { send: sinon.stub().returnsThis(), json: sinon.stub() };
const convertStub = sinon.stub();
const errorHandlerStub = sinon.stub().returns(fakeError);
const transformRequestStub = sinon.stub().returns(mReq.body);
const generate = proxyquire("./", {
"./convert": convertStub,
"./error-handler": errorHandlerStub,
"./request-converter": transformRequestStub
});
generate.generateReport(mReq, mRes);
convertStub.yield(fakeError, null);
sinon.assert.calledWith(transformRequestStub);
sinon.assert.calledWith(convertStub, {}, sinon.match.func);
sinon.assert.calledWith(errorHandlerStub, fakeError);
sinon.assert.calledWith(logSpy.firstCall, "HELLO");
sinon.assert.calledWith(logSpy.secondCall, "BYE");
});
});
});
Unit test result with coverage report:
report-exporter
generateReport
HELLO
BYE
✓ Should return an error response (94ms)
1 passing (98ms)
----------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------------------|----------|----------|----------|----------|-------------------|
All files | 87.5 | 50 | 62.5 | 87.5 | |
convert.js | 50 | 100 | 0 | 50 | 2 |
error-handler.js | 50 | 100 | 0 | 50 | 2 |
index.js | 84.62 | 50 | 100 | 84.62 | 14,15 |
index.spec.js | 100 | 100 | 100 | 100 | |
request-converter.js | 50 | 100 | 0 | 50 | 2 |
----------------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/47352972
Related
I have written a test code to test code that gives credentials from AWS Secret Manager. I used proxyquire and sinon for stubbing and getting this error.
Function I want to test
exports.getCredsFromAWSSecretsManager = (keyName) => {
const SM = new AWS.SecretsManager({
apiVersion: process.env.AWS_SM_API_VERSION,
region: process.env.AWS_SM_REGION
});
return SM.getSecretValue(params).promise().then((data) => {
logger.info(logMsgs.awsHlpr_smGetSecretValueSuccess(JSON.stringify(data)));
return JSON.parse(data.SecretString);
}).catch((err) => {
logger.error(logMsgs.awsHlpr_smGetSecretValueErr(JSON.stringify(err)));
throw err;
});
};
Test case that I have written
const sinon = require("sinon");
const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
const { mockLogger } = require("../../mockdata/mockLogger");
let awsHelper;
let secretsManagerStub;
describe.only("AWS Helper ", () => {
// function1
describe("AWS Helper: getCredsFromAWSSecretsManagera method", () => {
before((done) => {
const data = {
SecretString: JSON.stringify({ publicKey: 'secretUsername', privateKey: 'secretPassword' }),
};
secretsManagerStub = {
getSecretValue: sinon.stub().callsFake((params, callback) => {
callback(null, data);
}),
};
const awsStub = {
SecretsManager: sinon.stub().returns(secretsManagerStub)
}
awsHelper = proxyquire('../../../utils/aws_helper.js', {
'aws-sdk':{
AWS:awsStub
} ,
"../../utils/logger": mockLogger,
});
done();
});
afterEach(() => {
sinon.restore();
});
it('should write random data!', async () => {
const expectedData = "abcdef";
secretsManagerStub.getSecretValue.yields(null, expectedData);
const data = await awsHelper.getCredsFromAWSSecretsManager();
sinon.assert.callCount(secretsManagerStub.getSecretValue, 1);
assert.strictEqual(data, expectedData);
});
});
});
This code gives me the error saying
TypeError: AWS.SecretsManager is not a constructor
any help would be greatly appreciated.
AWS is a namespace, it contains all AWS service classes like SecretsManager. You should provide the awsStub to aws-sdk, there is no need to wrap the awsStub inside an object.
aws_helper.js:
const AWS = require('aws-sdk');
exports.getCredsFromAWSSecretsManager = () => {
const SM = new AWS.SecretsManager({
apiVersion: process.env.AWS_SM_API_VERSION,
region: process.env.AWS_SM_REGION,
});
const params = {
SecretId: '1',
};
return SM.getSecretValue(params)
.promise()
.then((data) => {
console.info(data);
return JSON.parse(data.SecretString);
})
.catch((err) => {
console.error(err);
throw err;
});
};
aws_helper.test.js:
const sinon = require('sinon');
const proxyquire = require('proxyquire').noCallThru().noPreserveCache();
let awsHelper;
let secretsManagerStub;
describe('AWS Helper: getCredsFromAWSSecretsManagera method', () => {
before(() => {
const data = {
SecretString: JSON.stringify({ publicKey: 'secretUsername', privateKey: 'secretPassword' }),
};
secretsManagerStub = {
getSecretValue: sinon.stub().returnsThis(),
promise: sinon.stub().resolves(data),
};
const awsStub = {
SecretsManager: sinon.stub().returns(secretsManagerStub),
};
awsHelper = proxyquire('./aws_helper.js', {
'aws-sdk': awsStub,
});
});
afterEach(() => {
sinon.restore();
});
it('should write random data!', async () => {
const data = await awsHelper.getCredsFromAWSSecretsManager();
sinon.assert.callCount(secretsManagerStub.getSecretValue, 1);
sinon.assert.match(data, { publicKey: 'secretUsername', privateKey: 'secretPassword' });
});
});
test result:
AWS Helper: getCredsFromAWSSecretsManagera method
{
SecretString: '{"publicKey":"secretUsername","privateKey":"secretPassword"}'
}
✓ should write random data!
1 passing (2s)
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 77.78 | 100 | 66.67 | 77.78 |
aws_helper.js | 77.78 | 100 | 66.67 | 77.78 | 19-20
---------------|---------|----------|---------|---------|-------------------
I am using fastify framework for my node.js application and sequelize as an ORM. I am using mocha, chai, and Sinon for unit testing. I have to unit test my controller function. The following is the sample controller function.
// controllers.js;
const service = require('../services');
exports.create = (req, reply) => {
const attributes = req.body;
service.create(attributes)
.then((result) => {
reply.code(201).send(result);
})
.catch((error) => {
reply.send(error);
});
};
and my services file is as follows,
// services.js;
const { Model } = require('../models');
function create(attributes) {
return Model.create(attributes);
}
module.exports = { create };
In the above code, I want to unit test only the 'create' function in controllers.js. The problem is, it should not call the database, since it is unit testing. But the Model.create in service.js file will make a call to the database. How can I unit test controller function only?
You should stub service.create method and create mocked req, reply objects.
E.g.
controller.js:
const service = require('./service');
exports.create = (req, reply) => {
const attributes = req.body;
service
.create(attributes)
.then((result) => {
reply.code(201).send(result);
})
.catch((error) => {
reply.send(error);
});
};
service.js:
const { Model } = require('./models');
function create(attributes) {
return Model.create(attributes);
}
module.exports = { create };
models.js:
const Model = {
create() {
console.log('real implementation');
},
};
module.exports = { Model };
controller.test.js:
const controller = require('./controller');
const service = require('./service');
const sinon = require('sinon');
const flushPromises = () => new Promise(setImmediate);
describe('62536251', () => {
afterEach(() => {
sinon.restore();
});
it('should create', async () => {
const mResult = 'success';
sinon.stub(service, 'create').resolves(mResult);
const mReq = { body: {} };
const mReply = { code: sinon.stub().returnsThis(), send: sinon.stub() };
controller.create(mReq, mReply);
await flushPromises();
sinon.assert.calledWith(mReply.code, 201);
sinon.assert.calledWith(mReply.send, 'success');
});
it('should handle error', async () => {
const mError = new Error('network');
sinon.stub(service, 'create').rejects(mError);
const mReq = { body: {} };
const mReply = { code: sinon.stub().returnsThis(), send: sinon.stub() };
controller.create(mReq, mReply);
await flushPromises();
sinon.assert.calledWith(mReply.send, mError);
});
});
unit test result with coverage report:
62536251
✓ should create
✓ should handle error
2 passing (13ms)
---------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------|---------|----------|---------|---------|-------------------
All files | 83.33 | 100 | 60 | 83.33 |
controller.js | 100 | 100 | 100 | 100 |
models.js | 66.67 | 100 | 0 | 66.67 | 3
service.js | 66.67 | 100 | 0 | 66.67 | 4
---------------|---------|----------|---------|---------|-------------------
I'm a beginner with JS tests and I'm having issues when I try to mockup the value of a constant in the file I need to test.
I have the following file
// index.js
const { MultiAccounts } = require('../../some.js')
const MultiAccountInstance = new MultiAccounts();
...
const syncEvents = () => Promise.try(() => {
// ...
return MultiAccountInstance.all()
.then((accounts) => // ...); // ==> it throws the exception here Cannot read property 'then' of undefined
});
module.exports = syncEvents;
So, I will like to mockup the MultiAccountInstance constant.
I had been trying using Simon and rewire, but with the following script I'm having it throws the exception here Cannot read property 'then' of undefined exception in the script above.
//index.test.js
const rewire = require('rewire');
const indexRewired = rewire('.../../index/js');
describe('testing sync events', () => {
let fakeMultiAccountInstance, MultiAccountInstanceReverter;
let accounts;
beforeEach(() => {
accounts = [{id: 1}, {id: 2}];
fakeMultiAccountInstance = {};
fakeMultiAccountInstance.all = () => Promise.resolve(accounts);
MultiAccountInstanceReverter = indexRewired.__set__('MultiAccountInstance', fakeMultiAccountInstance);
});
afterEach(() => {
MultiAccountInstanceReverter();
});
it('testing', ()=> {
const spy = sinon.stub(fakeMultiAccountInstance, 'all');
return indexRewired().then((resp) => {
spy.restore();
expect(spy).to.have.been.calledWith({someParams: true});
});
})
});
How can I achieve this?. I also tried using stubs, but I'm having the error that the MultiAccountInstance.all is not a function
it's something like this
//index.test.js
const rewire = require('rewire');
const indexRewired = rewire('.../../index/js');
describe('testing sync events', () => {
let stubMultiAccountInstance, MultiAccountInstanceReverter;
let accounts;
beforeEach(() => {
accounts = [{id: 1}, {id: 2}];
stubMultiAccountInstance= sinon.stub().returns({
all: () => Promise.resolve(accounts), // also tried with sinon.stub().resolves(accounts)
});
MultiAccountInstanceReverter = indexRewired.__set__('MultiAccountInstance', stubMultiAccountInstance);
});
afterEach(() => {
stubMultiAccountInstance.reset();
MultiAccountInstanceReverter();
});
it('testing', ()=> {
return indexRewired().then((resp) => {
expect(stubMultiAccountInstance).to.have.been.calledWith({someParams: true});
});
})
});
Do you know what am I doing wrong?
Here is the unit test solution:
index.js:
const { MultiAccounts } = require('./some.js');
const Promise = require('bluebird');
let MultiAccountInstance = new MultiAccounts();
const syncEvents = () =>
Promise.try(() => {
return MultiAccountInstance.all().then((accounts) => console.log(accounts));
});
module.exports = syncEvents;
some.js:
function MultiAccounts() {
async function all() {}
return {
all,
};
}
module.exports = { MultiAccounts };
index.test.js:
const sinon = require('sinon');
const rewire = require('rewire');
const Promise = require('bluebird');
describe('61659908', () => {
afterEach(() => {
sinon.restore();
});
it('should pass', async () => {
const promiseTrySpy = sinon.spy(Promise, 'try');
const logSpy = sinon.spy(console, 'log');
const indexRewired = rewire('./');
const accounts = [{ id: 1 }, { id: 2 }];
const fakeMultiAccountInstance = {
all: sinon.stub().resolves(accounts),
};
indexRewired.__set__('MultiAccountInstance', fakeMultiAccountInstance);
await indexRewired();
sinon.assert.calledOnce(fakeMultiAccountInstance.all);
sinon.assert.calledWith(logSpy, [{ id: 1 }, { id: 2 }]);
sinon.assert.calledOnce(promiseTrySpy);
});
});
unit test results with coverage report:
61659908
[ { id: 1 }, { id: 2 } ]
✓ should pass (54ms)
1 passing (62ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 80 | 100 |
index.js | 100 | 100 | 100 | 100 |
some.js | 100 | 100 | 50 | 100 |
----------|---------|----------|---------|---------|-------------------
I have the following classes A and B,
class A {
listenResponse(port, callback) {
const server = new CustomServer(port, (error, response) => {
if (error) return callback(error);
callback(null, response);
});
}
}
class CustomServer {
constructor(port, callback) {
this.port = port;
this.server = http.createServer((request, response) => {
if(// condition) { return callback(error); }
callback(null, response);
});
}
}
How do I test the function passed to CustomServer constructor while unit testing class A? Any help is highly appreciated.
Here is the unit test solution using an additional module proxyquire:
a.js:
const CustomServer = require('./customServer');
class A {
listenResponse(port, callback) {
const server = new CustomServer(port, (error, response) => {
if (error) return callback(error);
callback(null, response);
});
}
}
module.exports = A;
customServer.js:
class CustomServer {
constructor(port, callback) {
this.port = port;
this.server = http.createServer((request, response) => {
callback(null, response);
});
}
}
module.exports = CustomServer;
a.test.js:
const sinon = require('sinon');
const proxyquire = require('proxyquire');
describe('60084056', () => {
it('should get response', () => {
const error = null;
const response = {};
const customServerStub = sinon.stub().yields(error, response);
const A = proxyquire('./a.js', {
'./customServer': customServerStub,
});
const a = new A();
const port = 3000;
const callback = sinon.stub();
a.listenResponse(port, callback);
sinon.assert.calledWithExactly(customServerStub, port, sinon.match.func);
sinon.assert.calledWithExactly(callback, null, response);
});
it('should handle error', () => {
const error = new Error('network');
const response = {};
const customServerStub = sinon.stub().yields(error, response);
const A = proxyquire('./a.js', {
'./customServer': customServerStub,
});
const a = new A();
const port = 3000;
const callback = sinon.stub();
a.listenResponse(port, callback);
sinon.assert.calledWithExactly(customServerStub, port, sinon.match.func);
sinon.assert.calledWithExactly(callback, error);
});
});
Unit test results with coverage report:
60084056
✓ should get response (1684ms)
✓ should handle error
2 passing (2s)
-----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------------|---------|----------|---------|---------|-------------------
All files | 70 | 100 | 50 | 66.67 |
a.js | 100 | 100 | 100 | 100 |
customServer.js | 25 | 100 | 0 | 25 | 3-5
-----------------|---------|----------|---------|---------|-------------------
Source code: https://github.com/mrdulin/expressjs-research/tree/master/src/stackoverflow/60084056
When I run the test "Should call getDetails()" it fails with the message stating that the function was called 0 times. If I log the spy I can see that it its prop 'called' is equal to false. I know it is being called because I have a console.log in getDetails and it shows up each time getMultipleRecords() is called. The first test works just fine but findAll() is a promise, so I know it is an async issue.
I believe the issue is with mocha not respecting the await. Anyone's feedback would be greatly appreciated.
Test:
describe('getMultipleRecords()', () => {
let next = () => {};
let req = { body: { records: [1, 2, 3] } };
let res = {};
// Passing
it('Should call VMBilling.findAll()', async function() {
var findAll = sinon.spy(VMBilling, 'findAll');
await recordCtrl.getMultipleRecords(req, res, next);
//console.log(find)
sinon.assert.calledOnce(findAll);
});
// Failing
it('Should call getDetails()', async function() {
var gd = sinon.spy(recordCtrl, 'getDetails');
await recordCtrl.getMultipleRecords(req, res, next);
sinon.assert.calledOnce(gd);
});
});
Function that I am testing
const getMultipleRecords = async (req, res, next) => {
try {
console.log('getMultipleRecords()');
let records = await VMBilling.findAll({
include: [
{
model: ErrorMessage,
},
],
where: [
{
id: req.body.records,
},
],
});
let recordsWithDetails = getDetails(records);
console.log(recordsWithDetails);
return res.status(200).json(recordsWithDetails);
} catch (error) {
next(error);
}
}
Because after compilation the functions are exported with different signatures, with full name and while stubbing we stub the global function but while calling it from within the other function, we call the local function, hence it doesn’t work. There is a workaround to do that.
Here is the unit test solution:
controller.ts
import { VMBilling } from './model';
const getDetails = records => {
console.log(records);
};
const getMultipleRecords = async (req, res, next) => {
try {
console.log('getMultipleRecords()');
let records = await VMBilling.findAll({
include: [
{
model: 'ErrorMessage'
}
],
where: [
{
id: req.body.records
}
]
});
let recordsWithDetails = exports.getDetails(records);
console.log(recordsWithDetails);
return res.status(200).json(recordsWithDetails);
} catch (error) {
next(error);
}
};
exports.getMultipleRecords = getMultipleRecords;
exports.getDetails = getDetails;
model.ts:
export const VMBilling = {
findAll(conditions) {
console.log('conditions');
}
};
controller.spec.ts:
import { VMBilling } from './model';
import sinon from 'sinon';
const recordCtrl = require('./controller');
describe('getMultipleRecords()', () => {
let next = () => {};
let req = { body: { records: [1, 2, 3] } };
let res = {};
afterEach(() => {
sinon.restore();
});
it('Should call VMBilling.findAll()', async function() {
var findAll = sinon.spy(VMBilling, 'findAll');
await recordCtrl.getMultipleRecords(req, res, next);
sinon.assert.calledOnce(findAll);
});
it('Should call getDetails()', async function() {
var gd = sinon.spy(recordCtrl, 'getDetails');
await recordCtrl.getMultipleRecords(req, res, next);
sinon.assert.calledOnce(gd);
});
});
Unit test result with 100% coverage:
getMultipleRecords()
getMultipleRecords()
conditions
undefined
undefined
✓ Should call VMBilling.findAll()
getMultipleRecords()
conditions
undefined
undefined
✓ Should call getDetails()
2 passing (9ms)
--------------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
--------------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
controller.spec.ts | 100 | 100 | 100 | 100 | |
controller.ts | 100 | 100 | 100 | 100 | |
model.ts | 100 | 100 | 100 | 100 | |
--------------------|----------|----------|----------|----------|-------------------|
Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/58209098