Search code examples
javascripttestingmocha.jssinonstub

How do I stub a function that is not directly passed to the calling function?


I have an express app with API endpoints secured using JWT token. I have a method that verifies a received token.

// authentication.js

import jwt from 'jsonwebtoken';
import Settings from '../settings';

const AuthenticationMiddleware = {
    verifyToken: (req, res, next) => {
        const token = req.headers['x-access-token'];
        if (!token) {
            const msg = 'Include a valid token in the x-access-token header';
            return res.status(422).json({ 
                error: 'No token provided',
                msg 
            });
        }
        try {
            req.user = jwt.verify(token, Settings.jwtSecret);
            req.token = token;
            return next();
        }
        catch (e) {
            return res.status(422).json({ error: 'Invalid token' });
        }
    }
};

export default AuthenticationMiddleware;

This works fine when I call the API endpoints from postman with the token header included.

Now I have a test such as shown below. There's about 40 of them, each requiring a token to be sent with each API request.

// should is not used directly in the file but is added as a mocha requirement

import supertest from 'supertest';
import app from '../app';

const server = supertest.agent(app);
const BASE_URL = '/api/v1';

describe('/loans: Get all loans', () => {
    it('should return a list of all loans', done => {
        server
            .get(`${BASE_URL}/loans`)
            .expect(200)
            .end((err, res) => {
                res.status.should.equal(200);
                res.body.data.should.be.an.instanceOf(Array);
                for (const each of res.body.data) {
                    each.should.have.property('id');
                    each.should.have.property('userid');
                }
                done();
            });
    });
});

I've looked at sinon and tried stubbing the verifyToken function in mocha's before hook like so

import sinon from 'sinon';
import AuthenticationMiddleware from '../middleware/authentication';

before(() => {
    const stub = sinon.stub(AuthenticationMiddleware, 'verifyToken');
    stub.returnsThis()
});

But I can already see a problem here. While the verifyToken stub may have been created, it is NOT used during the test. The verifyToken that is being called during the test is passed as middleware from the route like so

router.get('/loans', AuthenticationMiddleware.verifyToken, LoansController.get_all_loans);

I want a way to stub verifyToken during the test so that I can just return next() immediately.

My question is, is it possible to stub AuthenticationMiddleware.verifyToken universally during the test so that all calls to the API endpoint call the stubbed version?


Solution

  • According to these two posts, Sinon stub being skipped as node express middleware and How to mock middleware in Express to skip authentication for unit test?, the reason for my stub not being active was that the app was being imported and cached before the stub is even created, so the app uses the one it has cached.

    So the solution was to alter the required function before the app gets a chance to cache it. What I did was that (I stumble upon it by trial and error) was to create a file in my test folder called stubs.js and here's the content.

    import sinon from 'sinon';
    import AuthenticationMiddleware from '../middleware/authentication';
    
    sinon.stub(AuthenticationMiddleware, 'verifyToken').callsFake(
        (req, res, next) => next()
    );
    

    Then I require this file in my test runner in package.json like so

        "scripts": {
            "test": "nyc --reporter=html --reporter=text --reporter=lcov mocha -r @babel/register -r should -r test/stubs.js"
        },