Sinon stub.resolves() not acting as desired - node.js

Here's the function I'm writing tests for:
ensureUserDoesNotExist(request, response, next) {
this.User.findOne({ where: { email: request.body.email }})
.then(user => {
if (user) {
response.sendStatus(403);
} else {
next();
}
});
}
And here's the test that I cannot get to pass:
it('should return a 403 if a matching user is found', () => {
mockRequest.body.email = 'test#email.com';
userController.User.findOne.resolves(true); // This is a previously created sinon stub
userController.ensureUserDoesNotExist(mockRequest, mockResponse, mockNext);
assert(mockResponse.sendStatus.calledWith(403));
});
It fails, simply claiming that the stub isn't called (at all, for what it's worth).
I strongly suspect this is to do with the promise - or Sinon's interaction with it - but am having a complete mind-blank in trying to figure out exactly what. The code works as intended (or it did when I last looked before playing about with it). Can anyone help me out?

Your assertion is evaluated before the end request
You need to return the promise
ensureUserDoesNotExist(request, response, next) {
return this.User.findOne({ where: { email: request.body.email }})
.then(user => {
if (user) {
response.sendStatus(403);
} else {
next();
}
});
}
and assert in then clause
it('should return a 403 if a matching user is found', () => {
mockRequest.body.email = 'test#email.com';
userController.User.findOne.resolves(true); // This is a previously created sinon stub
userController.ensureUserDoesNotExist(mockRequest, mockResponse, mockNext).then(() => {
assert(mockResponse.sendStatus.calledWith(403));
});
});

The test also must return a promise to indicate an asynchronous test to Mocha. You can use the one returned by the then call:
it('should return a 403 if a matching user is found', () => {
mockRequest.body.email = 'test#email.com';
userController.User.findOne.resolves(true); // This is a previously created sinon stub
return userController.ensureUserDoesNotExist(mockRequest, mockResponse, mockNext).then(() => {
assert(mockResponse.sendStatus.calledWith(403));
});
});

Related

Mocking in jest and express

I have some code in an Express route which talks to AWS Cognito and am having trouble working out how to mock it in tests.
cognitoExpress.validate(accessTokenFromClient, (err, response) => {
if (err) return res.status(401).json({ error: err });
res.json({ data: `Hello ${response.username}!` });
});
Then in my test I want to say cognitoExpress.validate should be called once and return {username: 'test user'} so that it doesnt hit the network and doesnt actually call AWS Cognito
it('It should returns 200 with a valid token', async done => {
const { cognitoExpress } = require('../helpers/cognitoExpress');
// I have tried
jest.mock('../helpers/cognitoExpress');
// and this
jest.mock('../helpers/cognitoExpress', () => ({
validate: jest.fn()
}));
const token = 'sfsfdsfsdfsd';
const response = await request.get('/').set('Authorization', token);
expect(cognitoExpress.validate).toHaveBeenCalledWith(token);
expect(response.body).toEqual({ data: 'Hello test user' });
done();
});
Thanks in advance....
let spyInstance = undefined;
beforeAll(() => {
spyInstance = jest.spyOn(cognitoExpress.prototype, "validate").mockImplementation(() => {
// Replace the body of 'validate' here, ensure it sets
// response body to {username: 'test user'} without calling AWS
...
});
});
afterAll(() => {
expect(spyInstance).toBeDefined();
expect(spyInstance).toHaveBeenCalledTimes(1);
jest.restoreAllMocks();
});
it("It should call mocked cognitoExpress.validate once", async done => {
...
});
A similar and working test in my project. Instead of cognitoExpress.validate it mocks and tests SampleModel.getData
Create file ../helpers/__mocks__/cognitoExpress.js with mocked function you want to use. It is essential to call the folder __mocks__. You can modify a functions and return any data you want.
example
module.exports = {
validate: () => { username: 'test user' }
}
Now you can use jest.mock('../helpers/cognitoExpress'), but I recommend you to place it to some global or test setup file, not to separate tests.
Jest Manual Mocks

Mock internal dependency

I have a request that has an internal dependency to a Facebook graph objects that performs another request against the FB graph API.
I'm wondering if it is possible to use sinon to mock the graph object so that it wouldn't actually perform a request in a test but would execute the callback function with a value that I provide in the test instead.
server.post("/facebookLogin", function(req, res) {
graph.setAccessToken(req.body.fbtoken);
graph.get("me?fields=email", function(err, obj) {
if (!err) {
var email = obj.email;
checkUserAlreadyRegistered(email, function(user) {
if (user) {
return res.send(200, {user:user, token: decorateToken(user.id)});
} else {
return res.send(404);
}
});
} else {
return res.send(500);
}
});
});
I had the exact same issue, and digging into the fbgraph source code I found out that even though it's using "graphql", internally is a network request with request so you can easily intercept it with nock:
// https://github.com/criso/fbgraph/blob/master/lib/graph.js#L34 <-- fb graph url
const fbMock = nock('https://graph.facebook.com/v4.0/')
.get('/me')
.query(true)
.reply(200, {
id: '123123',
name: 'fb username',
email: 'user#fb.com'
})
it('should not call fb"', (done) => {
chai.request(server)
.post('/facebookLogin')
.send({ fbtoken: 'token_fb' })
.end((err, res) => {
expect(err).to.be.null
expect(res).to.have.status(200)
expect(fbMock).to.have.been.requested
done()
})
}
note: the /v4.0/ part could be different depending on your configuration but the default value is 2.9 so be sure to use the same one you set with the setVersion method

NodeJS EventEmitter test with Mocha

I have a problem testing ldapjs client search operation. It returns an EventEmitter that you have to make listen for some specific event. I wrapped this operations to promisify it and to define my logic and I would like to unit-test it.
findUser(username) {
return new Promise((resolve, reject) => {
logger.debug('Searching user: ', username);
this.ldapClient.bind(user.name, .user.password, err => {
if (err) return reject(err);
else
this.ldapClient.search(root, {filter: `(cn=${username})`}, (errSearch, resSearch) => {
if (errSearch) return reject(errSearch);
const entries = [];
resSearch.on('searchEntry', entry => entries.push(entry.object));
resSearch.on('searchReference', referral => reject(new Error(`Received search referall: ${referral}`)));
resSearch.on('error', err => reject((err));
resSearch.on('end', result => {
if (result.status === 0 && entries.length === 1) {
return resolve({
cn: entries[0].cn,
objectclass: entries[0].objectclass,
password: entries[0].password
});
} else {
return reject(new Error(`Wrong search result: ${result}`));
}
});
});
});
});
}
I am using mockery and Sinon to replace ldapjs dependency inside my module:
beforeEach(function () {
searchEM = new EventEmitter();
sandbox = sinon.createSandbox();
ldapClientStub = Stubs.getLdapClientStub(sandbox);
ldapClientStub.bind.yields(null);
ldapClientStub.search.withArgs('o=ldap', {filter: `(cn=${findParam})`}).yields(null, searchEM);
mockery.registerMock('ldapjs', Stubs.getLdapStub(ldapClientStub));
mockery.registerAllowable('../src/client');
UserClientCls = require('../src/client').default;
userClient = new UserClientCls(config.get());
});
it('should return user with given username', function (done) {
setTimeout(() => {
searchEM.emit('searchEntry', users[1]);
searchEM.emit('end', {status: 0});
console.log('emitted');
}, 500);
searchEM.on('end', res => console.log(res));
userClient.findUser(findParam)
.then(user => {
user.cn.should.equal(users[1].attributes.cn);
user.objectclass.should.equal(users[1].attributes.objectclass);
user.password.should.equal(users[1].attributes.password);
return done();
})
.catch(err => done(err));
});
The problem is that listeners defined inside findUser are never called (but the function itself is called). The listener I defined in the test (just to debug the behaviour) is correctly called.
I do not understand if I miss something about how EventEmitters works or if I am doing the test in a wrong way. Or maybe I wrote a bad piece of code that cannot be tested.
I found a solution to my problem. I extended the base EventEmitter: I added the logic to store which event I want to emit and overrode its on method with a logic to emit my fake event.
class TestEventEmitter extends EventEmitter {
constructor() {
super();
}
setFakeEmit(fakeEmit) {
this.fakeEmit = fakeEmit;
}
on(eventName, cb) {
super.on(eventName, cb);
if (super.eventNames().length === 4)
this.fakeEmit.forEach(f => this.emit(f.name, f.obj));
}
}
So, in beforeEach I can stub ldapClientStub.search to make it return my TestEventEmitter:
beforeEach(function() {
searchEM = new TestEventEmitter();
searchEM.setFakeEmit([{
name: 'searchEntry',
obj: { object: users[1].attributes }
}, {
name: 'end',
obj: { status: 0 }
}]);
...
ldapClientStub.search.withArgs('o=ldap', { filter: `(&(cn=${findParam})(objectclass=astsUser))` }).yields(null, searchEM);
})
This solution may be not very elegant, but it works. If someone can post a better solution I'll be glad to have a look.

Error: Callback was already called in loopback

I have the following code:
"use strict";
const Raven = require("raven");
Raven.config(
"test"
).install();
module.exports = function(Reservation) {
function dateValidator(err) {
if (this.startDate >= this.endDate) {
err();
}
}
function sendEmail(campground) {
return new Promise((resolve, reject) => {
Reservation.app.models.Email.send(formEmailObject(campground),
function(
err,
mail
) {
if (err) {
console.log(err);
Raven.captureException(err);
reject(err);
} else {
console.log(mail);
console.log("email sent!");
resolve(mail);
}
});
});
}
function formEmailObject(campground) {
return {
to: "loopbackintern#yopmail.com",
from: "noreply#optis.be",
subject: "Thank you for your reservation at " + campground.name,
html:
"<p>We confirm your reservation for <strong>" +
campground.name +
"</strong></p>"
};
}
Reservation.validate("startDate", dateValidator, {
message: "endDate should be after startDate"
});
Reservation.observe("after save", async function(ctx, next) {
try {
const campground = await Reservation.app.models.Campground.findById(
ctx.instance.campgroundId
);
const mail = await sendEmail(campground);
next();
} catch (e) {
Raven.captureException(e);
next(e);
}
});
};
Sorry for the poor formatting. When the flow is done I get this error:
(node:3907) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Callback was already called.
I am calling the next() callback in two places, one in the try code and one in the catch code. I assume that when it all goes right, next callback is called only once, and the same when it goes wrong. But it seems that it is called twice and I don't know why.
I also tried to call next outside the try/catch code but it results in the same error. If I left only the next that is called inside the catch code it doesn't throw the error.
Any idea? Thanks!
if you are using async function you shouldn't explicitly call next, it gets automatically called.
check out this github issue for loopback async/await
so your hook can be like the following.
Reservation.observe("after save", async ctx => {
try {
const campground = await Reservation.app.models.Campground.findById(
ctx.instance.campgroundId
);
const mail = await sendEmail(campground);
} catch (e) {
Raven.captureException(e);
throw e;
}
});
NB: you don't need to wrap it in try catch unless you want to modify/work with the error.
You should declare your sendEmail method as async as it returns a promise.
async function sendEmail(campground) {
...
}
After reading this article, I created a await-handler.js file which include following code.
module.exports = (promise) =>
promise
.then(data => ({
ok: true,
data
}))
.catch(error =>
Promise.resolve({
ok: false,
error
})
);
Then in MyModel.js file, I created a async function to get a value from database as follow.
const awaitHandler = require("./../await-handler.js")
const getMaxNumber = async (MyModel) => {
let result = await awaitHandler(MyModel.find());
if (result.ok) {
if (result.data.length) {
return result.data.reduce((max, b) => Math.max(max, b.propertyName), result.data[0] && result.data[0].propertyName);
} else {
return 0;
}
} else {
return result.error;
}
}
As per #Mehari's answer, I've commented call to next() method as follow:-
module.exports = function(MyModel) {
MyModel.observe('before save', async(ctx, next) => {
const maxNumber = await getMaxNumber (MyModel);
if(ctx.instance) {
...
set the required property using ctx.instance.*
like createdAt, createdBy properties
...
// return next();
} else {
...
code for patch
...
// return next();
}
})
}
This solves the warning issue whenever saving endpoint is triggered.
But the warning issue still appear when I run the endpoint to load the resource.Like
http://localhost:3000/api/MyModel
Previously, the issue appear only when the before save operation hook gets triggered.
After encountering this issue, I checked adding access and loaded operation hooks and I found that the the warnings are issued after loaded operation hook.
MyModel.observe('access', (ctx, next) => {
return next();
})
MyModel.observe('loaded', (ctx, next) => {
return next();
})
What could have caused this issue and how can it gets resolved?

Using async await properly in node js

To overcome callback hell in javascript, I'm trying to use async await from legacy code written in SQLServer procedure.
But I'm not sure my code might be write properly.
My first confusing point is when async function returns, should it return resolve() as boolean, or just return reject and handle with try-catch?
Here is my code snippets.
Please correct me to right direction.
apiRoutes.js
app.route('/api/dansok/cancelDansok')
.post(dansokCancelHandler.cancelDansok);
dansokCancelController.js
const sequelize = models.Sequelize;
const jwt = require('jsonwebtoken');
async function jwtAccessAuthCheck(accessToken) {
if (!accessToken) {
return Promise.reject('Empty access token');
}
jwt.verify(accessToken,"dipa",function(err){
if(err) {
return Promise.reject('TokenExpiredError.');
} else {
return Promise.resolve();
}
});
}
async function checkFeeHist(dansokSeqNo) {
let feeHist = await models.FeeHist.findOne({
where: { DansokSeqNo: dansokSeqNo}
});
return !!feeHist;
}
async function getNextDansokHistSerialNo(dansokSeqNo) {
....
}
async function getDansokFee(dansokSeqNo) {
....
}
async function doCancel(dansokSeqNo) {
try {
if (await !checkFeeHist(dansokSeqNo)) {
log.error("doCancel() invalid dansokSeqNo for cancel, ", dansokSeqNo);
return;
}
let nextDansokSerialNo = await getNextDansokHistSerialNo(dansokSeqNo);
await insertNewDansokHist(dansokSeqNo, nextDansokSerialNo);
await updateDansokHist(dansokSeqNo);
await updateVBankList(dansokSeqNo, danokFee.VBankSeqNo);
await getVBankList(dansokSeqNo);
} catch (e) {
log.error("doCancel() exception:", e);
}
}
exports.cancelDansok = function (req, res) {
res.setHeader("Content-Type", "application/json; charset=utf-8");
const dansokSeqNo = req.body.DANSOKSEQNO;
const discKindCode = req.body.HISTKIND;
const worker = req.body.PROCWORKER;
const workerIp = req.body.CREATEIP;
const accessToken = req.headers.accesstoken;
//check input parameter
if (!dansokSeqNo || !discKindCode || !worker || !workerIp) {
let e = {status:400, message:'params are empty.'};
return res.status(e.status).json(e);
}
try {
jwtAccessAuthCheck(accessToken)
.then(() => {
log.info("jwt success");
doCancel(dansokSeqNo).then(() => {
log.info("cancelDansok() finish");
res.status(200).json({ message: 'cancelDansok success.' });
});
});
} catch(e) {
return res.status(e.status).json(e);
}
};
You'll need to rewrite jwtAccessAuthCheck(accessToken) so that it keeps track of the outcome of its nested tasks. In the code you've written:
// Code that needs fixes!
async function jwtAccessAuthCheck(accessToken) {
// This part is fine. We are in the main async flow.
if (!accessToken) {
return Promise.reject('Empty access token');
}
// This needs to be rewritten, as the async function itself doesn't know anything about
// the outcome of `jwt.verify`...
jwt.verify(accessToken,"dipa",function(err){
if(err) {
// This is wrapped in a `function(err)` callback, so the return value is irrelevant
// to the async function itself
return Promise.reject('TokenExpiredError.');
} else {
// Same problem here.
return Promise.resolve();
}
});
// Since the main async scope didn't handle anything related to `jwt.verify`, the content
// below will print even before `jwt.verify()` completes! And the async call will be
// considered complete right away.
console.log('Completed before jwt.verify() outcome');
}
A better rewrite would be:
// Fixed code. The outcome of `jwt.verify` is explicitly delegated back to a new Promise's
// `resolve` and `reject` handlers, Promise which we await for.
async function jwtAccessAuthCheck(accessToken) {
await new Promise((resolve, reject) => {
if (!accessToken) {
reject('Empty access token');
return;
}
jwt.verify(accessToken,"dipa",function(err){
if(err) {
reject('TokenExpiredError.');
} else {
resolve();
}
});
});
// We won't consider this async call done until the Promise above completes.
console.log('Completed');
}
An alternate signature that would also work in this specific use case:
// Also works this way without the `async` type:
function jwtAccessAuthCheck(accessToken) {
return new Promise((resolve, reject) => {
...
});
}
Regarding your cancelDansok(req, res) middleware, since jwtAccessAuthCheck is guaranteed to return a Promise (you made it an async function), you'll also need to handle its returned Promise directly. No try / catch can handle the outcome of this asynchronous task.
exports.cancelDansok = function (req, res) {
...
jwtAccessAuthCheck(accessToken)
.then(() => {
log.info("jwt success");
return doCancel(dansokSeqNo);
})
.then(() => {
log.info("cancelDansok() finish");
res.status(200).json({ message: 'cancelDansok success.' });
})
.catch(e => {
res.status(e.status).json(e);
});
};
I strongly suggest reading a few Promise-related articles to get the hang of it. They're very handy and powerful, but also bring a little pain when mixed with other JS patterns (async callbacks, try / catch...).
https://www.promisejs.org/
Node.js util.promisify

Resources