Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

wait for sinon stubbed promise to resolve before making assertion on sinon spy

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();
    });
});
like image 450
George Hattrell Avatar asked Oct 24 '17 13:10

George Hattrell


1 Answers

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.

like image 51
ElioRubens Avatar answered Oct 02 '22 02:10

ElioRubens