Stubbing middleware when testing an Express with Supertest

When testing a simple express app / api I like to use supertest.

This can feel like integration testing, and to an extent it is.

Let’s look at an example.

Suppose we have a simple API with the following router:

var express = require('express'),
app = express();

var router = express.Router();
app.use('/', require('./router'));

module.exports = app;
var express = require('express'),
router = express.Router(),
authentication = require('./authentication');

router.all('*', authentication.ensureAuthenticated);

router.get('/', function(req, res, next) {
    return res.send('Hello');
});

router.post('/', function(req, res, next) {
    return res.send('Posted');
});

module.exports = router;

To run the app, we have a separate server.js:

var app = require('./app');

var port = process.env.PORT || 8080;

app.listen(port, function() {
    console.log('Running on port ' + port);
});

This makes loading our app much easier in our supertest tests, as we’ll see later on.

Ok, nothing complex going on here – We can see we have some authentication middleware on all of our routes:

router.all('*', authentication.ensureAuthenticated);

This is some very basic authentication, for the purposes of our demo:

module.exports.ensureAuthenticated = function(req, res, next) {

    var authToken = req.get('x-auth-token');

    if (!authToken)
        return res.sendStatus(401);

    next();
}

Ok, all is good, our app runs, and if we don’t supply an authentication token in the header, we’re returned a 401, otherwise, we’re presented with ‘Hello’

Our tests currently look like this:
(see the full test at this version here)

describe('GET /', function() {
    describe('with authentication header set', function() {
        it('returns hello', function(done) {
            agent
                .get('/')
                .set('X-Auth-Token', 'xyz123')
                .expect(200)
                .end(function(err, res) {
                    if (err) return done(err);
                    assert(res.text == 'Hello')
                    done();
                });
        });
    });
    describe('without authentication header set', function() {
        it('returns a 401', function(done) {
            agent
                .get('/')
                .expect(401)
                .end(function(err, res) {
                    if (err) return done(err);
                    done();
                });
        });
    });
});

These all pass

sinon-express-supertest-tests-passing1

The testing for with / without authentication header set stuff bothers me;
For a start, it’s a cross cutting concern – it shouldn’t be tested like this.
Plus – we’re integration testing our authentication middleware.
What happens if/when we make this more complex?

Much better would be to stub this out.

So, by changing our test slightly as follows:

var ensureAuthenticatedSpy;

before(function() {

    //important to stub before we load our app
    ensureAuthenticatedSpy = sinon.stub(authentication, 'ensureAuthenticated');

    //this ensures we call our next() function on our middleware
    ensureAuthenticatedSpy.callsArg(2);

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

afterEach(function() {
    //assert that our middleware was called once for each test
    sinon.assert.calledOnce(ensureAuthenticatedSpy);

    ensureAuthenticatedSpy.reset();
})

describe('GET /', function() {
    it('returns hello', function(done) {
        agent
            .get('/')
            .set('X-Auth-Token', 'xyz123')
            .expect(200)
            .end(function(err, res) {
                if (err) return done(err);
                assert(res.text == 'Hello')
                done();
            });
    });
});

describe('POST /', function() {
    it('returns hello', function(done) {
        agent
            .post('/')
            .set('X-Auth-Token', 'xyz123')
            .expect(200)
            .end(function(err, res) {
                if (err) return done(err);
                assert(res.text == 'Posted')
                done();
            });
    });
});

We can create a stub of our ensureAuthenticated middleware.

Important here, is to note the removal of the “with authentication header set” tests… we don’t need them anymore! That’s taken care of by

afterEach(function() {
//assert that our middleware was called once for each test
sinon.assert.calledOnce(ensureAuthenticatedSpy);

Hope this helps!

I’ve added the full code on GitHub: GitHub