Colin
Colin

Reputation: 379

Why is my sinon stub acting like it's calling the real function?

I'm trying to follow this example: https://www.alexjamesbrown.com/blog/development/stubbing-middleware-testing-express-supertest/ but the sinon stub doesn't seem to be executing the wrapped code. I've seen lots of stackoverflow posts regarding this issue but none of the answers have helped me figure out what I'm doing wrong. Whenever I run my test I get the following error:

1) should return a list of sites

0 passing (42ms) 1 failing

  1. GET /api/config/buildPro/sites should return a list of sites: Error: expected 200 "OK", got 403 "Forbidden" at Test._assertStatus (node_modules\supertest\lib\test.js:268:12) at Test._assertFunction (node_modules\supertest\lib\test.js:283:11) at Test.assert (node_modules\supertest\lib\test.js:173:18) at Server.localAssert (node_modules\supertest\lib\test.js:131:12) at emitCloseNT (net.js:1655:8) at processTicksAndRejections (internal/process/task_queues.js:83:21)

This leads me to believe it's not calling the stub code but instead executes the actual authorization function. Here's my code:

app.js

const express = require('express');
const app = express();
const authorization = require('./security/authorization');

const configRoutes = require('./api/routes/config');

app.all('/api/*', authorization.authorize);
app.use('/api/config', configRoutes);

module.exports = app;

authorization.js

const aad = require('azure-ad-jwt');

module.exports.authorize = (req, res, next) => {
    if(!req.headers.authorization){
        res.status(403).json({
            message: "Auth failed"
        });
        return;
    }

    const jwtToken = req.headers.authorization.replace('Bearer ', '');

    aad.verify(jwtToken, null, function (err, result) {
        if (result) {
            next();
        } else {
            res.status(401).json({
                message: "Auth failed"
            });
        }
    });
};

config.spec.js

const request = require('supertest');
const sinon = require('sinon');
const app = require('../app');
const authorization = require('../security/authorization');

var agent;

describe('GET /api/names', () => {
    before(() => {
        ensureAuthenticatedSpy = sinon.stub(authorization, 'authorize');

        ensureAuthenticatedSpy.callsArgWithAsync(2);

        agent = require('supertest')
            .agent(require('../app'));
    });

    it('should return a list of names', done => {
        agent
            .get('/api/config/buildPro/sites')
            .expect(200)
            .end((err, res) => {
                if (err) return done(err);
                done();
            });
    });
});

Upvotes: 0

Views: 1902

Answers (1)

Zbigniew Zagórski
Zbigniew Zagórski

Reputation: 1981

instead executes the actual authorization

This is exactly what is happening.

Note this code in server:

app.all('/api/*', authorization.authorize);

This resolves authorize function reference in this particular state of program and express will use this particular function (original one!) for rest of program.

This:

ensureAuthenticatedSpy = sinon.stub(authorization, 'authorize');

is called later and given that sinon has no power to change references to original authorize captured previously ... is no-op.

IOW, dependency injection in basic Javascript apps is not as simple as one might want.

To workaround, you can change original route in app.js:

app.all('/api/*', (req, res, next) => authorization.authorize(req, res, next));

Now, your closure would resolve authorization.authorize every-time it's called, enabling mocking/spying function you're interested in. However this is solution is far from elegant.

Upvotes: 3

Related Questions