How does one unit test routes with Express?

Solution 1:

As others have recommended in comments, it looks like the canonical way to test Express controllers is through supertest.

An example test might look like this:

describe('GET /users', function(){
  it('respond with json', function(done){
    request(app)
      .get('/users')
      .set('Accept', 'application/json')
      .expect(200)
      .end(function(err, res){
        if (err) return done(err);
        done()
      });
  })
});

Upside: you can test your entire stack in one go.

Downside: it feels and acts a bit like integration testing.

Solution 2:

I've come to the conclusion that the only way to really unit test express applications is to maintain a lot of separation between the request handlers and your core logic.

Thus, your application logic should be in separate modules that can be required and unit tested, and have minimal dependence on the Express Request and Response classes as such.

Then in the request handlers you need to call appropriate methods of your core logic classes.

I'll put an example up once I've finished restructuring my current app!

I guess something like this? (Feel free to fork the gist or comment, I'm still exploring this).

Edit

Here's a tiny example, inline. See the gist for a more detailed example.

/// usercontroller.js
var UserController = {
   _database: null,
   setDatabase: function(db) { this._database = db; },

   findUserByEmail: function(email, callback) {
       this._database.collection('usercollection').findOne({ email: email }, callback);
   }
};

module.exports = UserController;

/// routes.js

/* GET user by email */
router.get('/:email', function(req, res) {
    var UserController = require('./usercontroller');
    UserController.setDB(databaseHandleFromSomewhere);
    UserController.findUserByEmail(req.params.email, function(err, result) {
        if (err) throw err;
        res.json(result);
    });
});

Solution 3:

Change your response object:

var response = {
    viewName: ""
    , data : {}
    , render: function(view, viewData) {
        this.viewName = view;
        this.data = viewData;
    }
};

And it will work.

Solution 4:

The easiest way to test HTTP with express is to steal TJ's http helper

I personally use his helper

it("should do something", function (done) {
    request(app())
    .get('/session/new')
    .expect('GET', done)
})

If you want to specifically test your routes object, then pass in correct mocks

describe("Default Route", function(){
    it("should provide the a title and the index view name", function(done){
        routes.index({}, {
            render: function (viewName) {
                viewName.should.equal("index")
                done()
            }
        })
    })
})