Saturday, 8 April 2017

Testing Express routes with Sinon stubs

I'm trying to unit test Express routes to verify they call the correct controller functions. At this point, I'm not checking the internal logic of the controller functions, only that the routes map to the right functions. What I have so far works perfectly until I introduce router middleware like so:

'use strict';

import express from 'express';
import config from './../../config/environments';
import SuperLogin from 'superlogin';

let controller = require('./subjects.controller');
let router = express.Router();
let superlogin = new SuperLogin(config);

router.get('/', superlogin.requireAuth, superlogin.requireAnyRole(['admin', 'specialist', 'nurse']), controller.index);
router.get('/:subjectId', superlogin.requireAuth, superlogin.requireAnyRole(['admin', 'specialist', 'nurse']), controller.show);
router.post('/', superlogin.requireAuth, superlogin.requireAnyRole(['admin', 'specialist', 'nurse']), controller.create);
router.put('/:subjectId', superlogin.requireAuth, superlogin.requireAnyRole(['admin', 'specialist', 'nurse']), controller.upsert);
router.patch('/:subjectId', superlogin.requireAuth, superlogin.requireAnyRole(['admin', 'specialist', 'nurse']), controller.patch);
router.delete('/:subjectId', superlogin.requireAuth, superlogin.requireAnyRole(['admin']), controller.destroy);


module.exports = router;

As you can see I'm using Superlogin to verify access to each route. If I remove this my tests pass, adding the middleware causes them to fail. I probably need to stub the superlogin methods but I don't know where to do this.

My test is as follows

'use strict';

/* globals sinon, describe, expect, it */

let proxyquire = require('proxyquire').noPreserveCache();

let subjectCtrlStub = {
    index: 'subjectCtrl.index',
    show: 'subjectCtrl.show',
    create: 'subjectCtrl.create',
    upsert: 'subjectCtrl.upsert',
    patch: 'subjectCtrl.patch',
    destroy: 'subjectCtrl.destroy'
};

sinon.stub(superlogin, 'requireAuth', function(req, res, next) {
    return next();
});

let routerStub = {
    get: sinon.spy(),
    put: sinon.spy(),
    patch: sinon.spy(),
    post: sinon.spy(),
    delete: sinon.spy()
};

// require the index with our stubbed out modules
let subjectRoutes = proxyquire('./subjects.routes.js', {
    express: {
        Router() {
            return routerStub;
        }
    },
    './subjects.controller': subjectCtrlStub
});

describe('Subject API Router:', function() {

    it('should return an express router instance', function() {
        subjectRoutes.should.equal(routerStub);
    });

    describe('GET /api/subjects', function() {
        it('should route to subjects.controller.index', function() {
            routerStub.get
                .withArgs('/', 'subjectCtrl.index')
                .should.have.been.calledOnce;
        });
    });

    describe('GET /api/subjects/:subjectId', function() {
        it('should route to subjects.controller.show', function() {
            routerStub.get
                .withArgs('/:subjectId', 'subjectCtrl.show')
                .should.have.been.calledOnce;
        });
    });

    describe('POST /api/subjects', function() {
        it('should route to subjects.controller.create', function() {
            routerStub.post
                .withArgs('/', 'subjectCtrl.create')
                .should.have.been.calledOnce;
        });
    });

    describe('PUT /api/subjects/:subjectId', function() {
        it('should route to subjects.controller.upsert', function() {
            routerStub.put
                .withArgs('/:subjectId', 'subjectCtrl.upsert')
                .should.have.been.calledOnce;
        });
    });

    describe('PATCH /api/subjects/:subjectId', function() {
        it('should route to subjects.controller.patch', function() {
            routerStub.patch
                .withArgs('/:subjectId', 'subjectCtrl.patch')
                .should.have.been.calledOnce;
        });
    });

    describe('DELETE /api/subjects/:subjectId', function() {
        it('should route to subjects.controller.destroy', function() {
            routerStub.delete
                .withArgs('/:subjectId', 'subjectCtrl.destroy')
                .should.have.been.calledOnce;
        });
    });
});

I have a before function that runs ahead of all of these tests that seeds the database with some example users with different roles and saves Authorization tokens to a global variable for use when testing the endpoints, I just don't know how to send them with the routerStub function calls.



via Mark

No comments:

Post a Comment