Reputation: 91
I have a middleware function which checks a session token to see if the user is an admin user. The function does not return anything if all checks pass but simply calls next().
How can I wait for the internal asynchronous Promise (adminPromise) to resolve before making assertions on the next() callback which is a Sinon spy? The test will currently fail because the assertion in the test is made before the resolution of the promise in AdminMiddleware.prototype.run.
The function is:
AdminMiddleware.prototype.run = function (req, res, next) {
let token = req.header(sessionTokenHeader);
let adminPromise = new admin().send()
adminPromise.then(function (authResponse) {
let adminBoolean = JSON.parse(authResponse).payload.admin;
if (adminBoolean !== true) {
return new responseTypes().clientError(res, {
'user': 'validation.admin'
}, 403);
};
next();
});
};
And the test:
it('should call next once if admin', function (done) {
stub = sinon.stub(admin.prototype, 'send');
stub.resolves(JSON.stringify({success : true, payload : {admin : true}}));
let nextSpy = sinon.spy();
AdminMiddleware.prototype.run({header: function () {}}, {}, nextSpy);
expect(nextSpy.calledOnce).to.be.true;
done();
});
At the moment I am wrapping the expectation like below, which will cause the test to pass, but seems like a hack. Furthermore, if it were to fail it would cause an unhandled promise rejection error and timeout due to done() not being called.
it('should call next once if admin', function (done) {
stub = sinon.stub(admin.prototype, 'send');
stub.resolves(JSON.stringify({success : true, payload : {admin : true}}));
let nextSpy = sinon.spy();
AdminMiddleware.prototype.run({header: function () {}}, {}, nextSpy);
stub().then(function () {
expect(nextSpy.calledOnce).to.be.true;
done();
});
});
Upvotes: 9
Views: 7195
Reputation: 701
One solution is to use a stub instead of a spy. Although they are two different things, I don't see you losing any functionality in this case as you were using an anonymous spy.
AdminMiddleware.prototype.run = function (req, res, next) {
const adminPromise = Promise.resolve()
adminPromise.then(function (authResponse) {
next()
})
}
it('should call next once if admin', function (done) {
const nextSpy = sinon.stub()
nextSpy.callsFake(() => {
expect(nextSpy.called).to.be.true
done()
})
AdminMiddleware.prototype.run({header: function () {}}, {}, nextSpy);
});
Also, you could avoid using sinon altogether for this assertion and do something like:
it('should call next once if admin', function (done) {
let called
const nextSpy = function() {
called = true
expect(called).to.be.true
done()
}
AdminMiddleware.prototype.run({header: function () {}}, {}, nextSpy);
});
In these two examples you will still get a timeout error if nextSpy isn't called, can't think of any reasonable way around that. If the unhandled promise rejection was very important to avoid I suppose you could do something like this:
nextSpy.callsFake(() => {
try {
expect(nextSpy.called).to.be.false
done()
} catch (e) {
console.log(e)
}
})
It would still fail the test due to the timeout, however it would not throw an unhandled promise rejection error.
Upvotes: 5