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.
But we can stub things out with sinon, and tidy up our tests.

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

5 responses to “Stubbing middleware when testing an Express with Supertest”

  1. Pete Jeffryes avatar
    Pete Jeffryes

    Hello Alex, Thanks for the tutorial. I am trying to replicate this in my app and the middleware always gets called despite the stub. I have ensured that the method is stubbed by stubbing it a second time and confirming I get the …can’t wrap a method that is already wrapped…error. Any gotchas that you know of?

    1. Andrej Zachar avatar

      I have the same issue, do you have any idea why?

    2. Alex avatar
      Alex

      hey, sorry for delay in replying – I’m not sure about the error
      Did you manage to resolve it?

      1. Pete Jeffryes avatar
        Pete Jeffryes

        no, never did, just ended up bypassing the auth if the env was ‘test’

    3. Mr. Doomsbuster avatar

      I am facing similar issue. The required module does not get replaced with the stubbed version for some reason. Here is my code:

      “`javascript
      ‘use strict’;
      const request = require(‘supertest’);
      var service = require(‘../services/SampleService’)
      const sinon = require(‘sinon’);
      const helper = require(‘./helpers/common’);
      const chai = require(‘chai’);
      var stub;
      var agent;

      describe(‘Application Route’, function() {
      describe(“GET /api/retrieve”, function() {
      beforeEach(function() {
      stub = sinon.stub(service,’mySampleGetService’);
      stub.returns({
      “name”:”myname”
      });
      agent = require(‘supertest’).agent(‘../app’);
      });

      it(‘should return 500 if service call fails’, function(done) {
      agent.get(‘/api/retrieve’)
      .expect(500)
      .end(function(err, res) {
      helper.end(err, res, done);
      });
      });

      afterEach(function() {
      stub.restore();
      });
      });
      });
      “`

Leave a Reply

Your email address will not be published. Required fields are marked *