How do I test something like this?
const ssmParameterData = await ssm.getParameter(params, async (error, data) => {
if (error) throw error;
return data;
}).promise();
I have tried just mocking the method
spyOn(ssm, 'getParameter').and.returnValue(ssmParams);
and I get error like
TypeError: Cannot read property 'promise' of undefined
Here is the unit test solution:
index.js:
const AWS = require('aws-sdk');
const ssm = new AWS.SSM();
async function main(params) {
const ssmParameterData = await ssm
.getParameter(params, (error, data) => {
if (error) throw error;
return data;
})
.promise();
return ssmParameterData;
}
module.exports = { ssm, main };
index.test.js:
const { ssm, main } = require('./');
describe('60138152', () => {
it('should pass', async () => {
const data = 'fake data';
const getParameterRequestStub = { promise: jasmine.createSpy('promise') };
const getParameterStub = spyOn(ssm, 'getParameter').and.callFake((params, callback) => {
callback(null, data);
getParameterRequestStub.promise.and.resolveTo(data);
return getParameterRequestStub;
});
await expectAsync(main('test params')).toBeResolvedTo('fake data');
expect(getParameterStub).toHaveBeenCalledWith('test params', jasmine.any(Function));
expect(getParameterRequestStub.promise).toHaveBeenCalledTimes(1);
});
it('should throw error', async () => {
const mError = new Error('network');
const getParameterRequestStub = { promise: jasmine.createSpy('promise') };
const getParameterStub = spyOn(ssm, 'getParameter').and.callFake((params, callback) => {
callback(mError);
getParameterRequestStub.promise.and.rejectWith(mError);
return getParameterRequestStub;
});
await expectAsync(main('test params')).toBeRejectedWithError('network');
expect(getParameterStub).toHaveBeenCalledWith('test params', jasmine.any(Function));
});
});
unit test results with 100% coverage:
Executing 2 defined specs...
Running in random order... (seed: 87758)
Test Suites & Specs:
(node:93291) ExperimentalWarning: The fs.promises API is experimental
1. 60138152
✔ should pass (10ms)
✔ should throw error (1ms)
>> Done!
Summary:
👊 Passed
Suites: 1 of 1
Specs: 2 of 2
Expects: 5 (0 failures)
Finished in 0.034 seconds
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
source code: https://github.com/mrdulin/jasmine-examples/tree/master/src/stackoverflow/60138152
Related
In code below I'm trying to test method getParameter for failure. Module A contains the method to test.
Module A.spec contains the test. Problem is that the test always passes, meaning it never hiuts the catch. I am mocking AWS.SSM to fail. What am I missing here?
Module A:
const AWS = require ('aws-sdk')
exports.getParameter = async function (parameterName) {
const params = {
Name: parameterName,
WithDecryption: true
};
const ssm = new AWS.SSM();
try {
const paramValue = await ssm.getParameter(params).promise();
return paramValue.Parameter.Value;
}
catch (e) {
console.log(e);
throw new Error('Error while retrieving parameter value from pstore.\n' + e);
}
};
exports.AWS = AWS
Module A.spec.js
const prm = require('A')
test('should handle error', () => {
prm.AWS.SSM = jest.fn().mockImplementation(() => {
throw new Error()
})
prm.getParameter = jest.fn()
try{
prm.getParameter("abc")
}
catch(e)
{
console.log(e)
}
});
You can use jest.spyOn(prm.AWS, 'SSM').mockReturnValue() to mock the SSM constructor and its instance.
E.g.
a.js:
const AWS = require('aws-sdk');
exports.getParameter = async function (parameterName) {
const params = {
Name: parameterName,
WithDecryption: true,
};
const ssm = new AWS.SSM();
try {
const paramValue = await ssm.getParameter(params).promise();
return paramValue.Parameter.Value;
} catch (e) {
console.log(e);
throw new Error('Error while retrieving parameter value from pstore.\n' + e);
}
};
exports.AWS = AWS;
a.spec.js:
const prm = require('./a');
describe('71027434', () => {
afterEach(() => {
jest.restoreAllMocks();
});
test('should get parameter value', async () => {
const ssm = {
getParameter: jest.fn().mockReturnThis(),
promise: jest.fn().mockResolvedValueOnce({ Parameter: { Value: 'fake value' } }),
};
const SSMSpy = jest.spyOn(prm.AWS, 'SSM').mockReturnValue(ssm);
const actual = await prm.getParameter('abc');
expect(actual).toEqual('fake value');
expect(SSMSpy).toBeCalledTimes(1);
expect(ssm.getParameter).toBeCalledWith({ Name: 'abc', WithDecryption: true });
});
test('should handle error', async () => {
const mError = new Error('network');
const ssm = {
getParameter: jest.fn().mockReturnThis(),
promise: jest.fn().mockRejectedValueOnce(mError),
};
const SSMSpy = jest.spyOn(prm.AWS, 'SSM').mockReturnValue(ssm);
await expect(prm.getParameter('abc')).rejects.toThrowError(
'Error while retrieving parameter value from pstore.\n' + mError
);
expect(SSMSpy).toBeCalledTimes(1);
expect(ssm.getParameter).toBeCalledWith({ Name: 'abc', WithDecryption: true });
});
});
Test result:
PASS stackoverflow/71027434/a.spec.js (8.108 s)
71027434
✓ should get parameter value (4 ms)
✓ should handle error (23 ms)
console.log
Error: network
at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/stackoverflow/71027434/a.spec.js:20:20
at Generator.next (<anonymous>)
at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/stackoverflow/71027434/a.spec.js:8:71
at new Promise (<anonymous>)
at Object.<anonymous>.__awaiter (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/stackoverflow/71027434/a.spec.js:4:12)
at Object.<anonymous> (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/stackoverflow/71027434/a.spec.js:19:42)
at Object.asyncJestTest (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:106:37)
at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/queueRunner.js:45:12
at new Promise (<anonymous>)
at mapper (/Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/queueRunner.js:28:19)
at /Users/dulin/workspace/github.com/mrdulin/jest-v26-codelab/node_modules/jest-jasmine2/build/queueRunner.js:75:41
at Object.<anonymous> (stackoverflow/71027434/a.js:13:13)
at Generator.throw (<anonymous>)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
a.js | 100 | 100 | 100 | 100 |
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 8.623 s, estimated 10 s
package version:
"aws-sdk": "^2.875.0",
"jest": "^26.6.3",
I'm testing the NodeJS function/module below using jestjs
const fs = require("fs");
const path = require("path");
function read(filename, callback) {
fs.readFile(path.join(__dirname, filename), { encoding: "utf-8" }, callback);
}
module.exports = {
read,
};
The first test is:
test("callback data returned is correct", (done) => {
function callback(err, data) {
try {
expect(data).toBe("1");
done();
} catch (err) {
done(err);
}
}
read("./test", callback);
});
and is succesful (I have an actual real test file with the contents being 1.
The second one is a bit trickier. Since I'm providing a mockCallback function to read, why isn't the callback called?
const { read } = require("./callback");
describe("callback testing", () => {
test("callback is called", (done) => {
const mockCallback = jest.fn((err, data) => data);
read("./test", mockCallback);
expect(mockCallback).toHaveBeenCalled();
done();
});
});
The test fails and while inspecting the mock function it seems it wasn't called.
I also tried adding the following in order to mock fs.readFile, without much success:
const mock = require("mock-fs");
const fs = require("fs");
jest.mock("fs");
beforeEach(() => {
mock({
test: "1",
});
});
I'd like a solution that mocks the least of the dependant methods and an explanation as to why isn't the callback run.
You should mock fs.readFile method and trigger the callback in your test case.
E.g.
callback.js:
const fs = require('fs');
const path = require('path');
function read(filename, callback) {
fs.readFile(path.join(__dirname, filename), { encoding: 'utf-8' }, callback);
}
module.exports = {
read,
};
callback.test.js:
const { read } = require('./callback');
const fs = require('fs');
describe('callback testing', () => {
test('callback is called', () => {
const mockCallback = jest.fn();
const mockData = 'mock file data';
const mockReadFile = jest.spyOn(fs, 'readFile').mockImplementationOnce((filename, options, callback) => {
callback(null, mockData);
});
read('./test', mockCallback);
expect(mockCallback).toHaveBeenCalled();
expect(mockReadFile).toBeCalledWith(expect.any(String), { encoding: 'utf-8' }, mockCallback);
mockReadFile.mockRestore();
});
});
unit test result:
PASS examples/65361608/callback.test.js
callback testing
✓ callback is called (3 ms)
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
callback.js | 100 | 100 | 100 | 100 |
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.992 s, estimated 5 s
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
---------------|---------|----------|---------|---------|-------------------
For the source code(dynamodb.js):
const AWS = require("aws-sdk");
const Promise = require("bluebird");
const client = new AWS.DynamoDB.DocumentClient();
module.exports.db = (method, params) => {
console.log("access dynamodb ");
return Promise.fromCallback(cb => client[method](params, cb));
};
Using the test that looks like this(dont want't to mock Promise.fromCallback):
describe("test", () => {
const realAWS = require("aws-sdk");
let fakePromise;
let fakeDynamo;
let dbClient;
beforeAll(function() {
fakePromise = jest.fn();
fakeDynamo = {
get: (params, cb) => {
fakePromise(params, cb);
}
};
realAWS.DynamoDB.DocumentClient = jest.fn(() => fakeDynamo);
dbClient = require("../dynamodb");
});
test.only("Test successed", done => {
let result = dbClient.db("get", null);
console.log("access dynamodb ");
expect(fakePromise).toHaveBeenCalled();
});
});
but when tun the test ,the error was heppend:
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
if anyone can help me? Thanks!
You need to mock the implementation of the client.get method as a node callback style. So that Promise.fromCallback method will covert callback style to a promise.
Besides, you need to trigger the callback in your test case. That's why you got a timeout error.
E.g.
dynamodb.js:
const AWS = require('aws-sdk');
const Promise = require('bluebird');
const client = new AWS.DynamoDB.DocumentClient();
module.exports.db = (method, params) => {
console.log('access dynamodb');
return Promise.fromCallback((cb) => client[method](params, cb));
};
dynamodb.test.js:
const { db } = require('./dynamodb');
const AWS = require('aws-sdk');
jest.mock('aws-sdk', () => {
const mClient = { get: jest.fn() };
const mDynamoDB = {
DocumentClient: jest.fn(() => mClient),
};
return { DynamoDB: mDynamoDB };
});
const mockClient = new AWS.DynamoDB.DocumentClient();
describe('54360588', () => {
afterAll(() => {
jest.resetAllMocks();
});
it('should pass', async () => {
mockClient.get.mockImplementationOnce((err, callback) => {
callback(null, 'fake data');
});
const actual = await db('get', null);
expect(actual).toBe('fake data');
expect(mockClient.get).toBeCalledWith(null, expect.any(Function));
});
});
unit test result with coverage report:
PASS src/stackoverflow/54360588/dynamodb.test.js (10.74s)
54360588
✓ should pass (28ms)
console.log src/stackoverflow/54360588/dynamodb.js:177
access dynamodb
-------------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
-------------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
dynamodb.js | 100 | 100 | 100 | 100 | |
-------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 12.636s
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