67

I've just added shouldjs and mocha to my express app for testing, but I'm wondering how to test my application. I would like to do it like this:

app = require '../app'
routes = require '../src/routes'

describe 'routes', ->
  describe '#show_create_user_screen', ->
    it 'should be a function', ->
      routes.show_create_user_screen.should.be.a.function
    it 'should return something cool', ->
      routes.show_create_user_screen().should.be.an.object

Of course, the last test in that test-suite just tells med that the res.render function (called within show_create_user_screen) is undefined, probably becouse the server is not running and the config has not been done. So I wonder how other people set up their tests?

Robin Heggelund Hansen
  • 4,906
  • 6
  • 37
  • 54
  • Just like to add that the above example was posted becouse it was short and concise. Usually I would test that the appropriate functions or values on the given req/res objects was set/called after one of my router functions is called. For which the answer below is adequate. One shouldn't test the router functionality, that is the job of the web-framework. – Robin Heggelund Hansen Jun 26 '12 at 10:37

5 Answers5

66

found an alternative in connect.js tests suites

They are using supertest to test a connect app without binding the server to any port and without using mock-ups.

Here is an excerpt from connect's static middleware test suite (using mocha as the test runner and supertest for assertions)

var connect = require('connect');

var app = connect();
app.use(connect.static(staticDirPath));

describe('connect.static()', function(){
  it('should serve static files', function(done){
    app.request()
    .get('/todo.txt')
    .expect('contents', done);
  })
});

This works for express apps as well

alexandru.topliceanu
  • 2,364
  • 2
  • 27
  • 38
  • I can only accept one answer, otherwise this would be accepted as well =) – Robin Heggelund Hansen Jul 17 '12 at 09:54
  • 1
    app.request didn't work for me in the latest express/connect, so I've updated this answer to match the usage at https://github.com/visionmedia/supertest – Aaron Jan 23 '13 at 06:32
  • Clause about `supertest` looks misleading. There seems to be no mention of it in [connect code](https://github.com/search?q=supertest+repo%3A%22senchalabs%2Fconnect%22&type=Code&ref=searchresults). Anyway Alexandru's answer looks better than the others. – yanychar Jan 31 '14 at 21:38
  • Here's the part of connect that uses `supertest`: https://github.com/senchalabs/connect/blob/2fe55712b6d204dd8a9132d29685f0c4e6be48db/test/server.js#L5 – Félix Saparelli Mar 18 '15 at 01:05
33

OK, first although testing your routing code is something you may or may not want to do, in general, try to separate your interesting business logic in pure javascript code (classes or functions) that are decoupled from express or whatever framework you are using and use vanilla mocha tests to test that. Once you've achieved that if you want to really test the routes you configure in mocha, you need to pass mock req, res parameters into your middleware functions to mimic the interface between express/connect and your middleware.

For a simple case, you could create a mock res object with a render function that looks something like this.

describe 'routes', ->
  describe '#show_create_user_screen', ->
    it 'should be a function', ->
      routes.show_create_user_screen.should.be.a.function
    it 'should return something cool', ->
      mockReq = null
      mockRes =
        render: (viewName) ->
          viewName.should.exist
          viewName.should.match /createuser/

      routes.show_create_user_screen(mockReq, mockRes).should.be.an.object

Also just FYI middleware functions don't need to return any particular value, it's what they do with the req, res, next parameters that you should focus on in testing.

Here is some JavaScript as you requested in the comments.

describe('routes', function() {
    describe('#show_create_user_screen', function() {
      it('should be a function', function() {
        routes.show_create_user_screen.should.be.a["function"];
      });
      it('should return something cool', function() {
        var mockReq = null;
        var mockRes = {
          render: function(viewName) {
            viewName.should.exist;
            viewName.should.match(/createuser/);
          }
        };
        routes.show_create_user_screen(mockReq, mockRes);
      });
    });
  });
nackjicholson
  • 4,557
  • 4
  • 37
  • 35
Peter Lyons
  • 142,938
  • 30
  • 279
  • 274
  • 1
    One thing that mocking doesn't give you is protection against api changes of the module you're using. e.g. if express updates and changes the name of render, you're not protected. Ideally you're testing that as well, but sometimes integration+unit tests can test a lot of code at once, which is a good or a bad thing depending on how you look at it. edit: Though I really like this mocking method, it's really lightweight. – timoxley Apr 02 '12 at 12:23
  • 64
    Could please always also add compiled js, some people are not familiar with reading coffeescript. – Julius F Oct 21 '12 at 11:45
  • Isn't this testing an implementation detail ? You actually want to test what the 'response' object contains when it is returned - if in the future you do not use 'render' method to do this, for example during a refactor your test will fail and not show you that your refactored code works, as you'll have to rewrite the test ? Just a thought ! Otherwise it's a clever way of mocking out the response object. – SimonB Jul 04 '14 at 14:19
  • Supertest is another approach for more end to end testing. Both have their uses. – Peter Lyons Jul 04 '14 at 16:07
24

You could try SuperTest, and then server start-up and shutdown are taken care of:

var request = require('supertest')
  , app     = require('./anExpressServer').app
  , assert  = require("assert");

describe('POST /', function(){
  it('should fail bad img_uri', function(done){
    request(app)
        .post('/')
        .send({
            'img_uri' : 'foobar'
        })
        .expect(500)
        .end(function(err, res){
            done();
        })
  })
});
Lee Goddard
  • 10,680
  • 4
  • 46
  • 63
7

mocha comes with before, beforeEach, after, and afterEach for bdd testing. In this case you should use before in your describe call.

describe 'routes' ->
  before (done) ->
    app.listen(3000)
    app.on('connection', done)
fent
  • 17,861
  • 15
  • 87
  • 91
6

I've found it's easiest to set up a TestServer class to be used as a helper, as well as a helper http client, and just make real requests to a real http server. There may be cases where you want to mock and stub this stuff instead though.

// Test file
var http = require('the/below/code');

describe('my_controller', function() {
    var server;

    before(function() {
        var router = require('path/to/some/router');
        server = http.server.create(router);
        server.start();
    });

    after(function() {
        server.stop();
    });

    describe("GET /foo", function() {
        it('returns something', function(done) {
            http.client.get('/foo', function(err, res) {
                // assertions
                done();
            });
        });
    });
});


// Test helper file
var express    = require('express');
var http       = require('http');

// These could be args passed into TestServer, or settings from somewhere.
var TEST_HOST  = 'localhost';
var TEST_PORT  = 9876;

function TestServer(args) {
    var self = this;
    var express = require('express');
    self.router = args.router;
    self.server = express.createServer();
    self.server.use(express.bodyParser());
    self.server.use(self.router);
}

TestServer.prototype.start = function() {
    var self = this;
    if (self.server) {
        self.server.listen(TEST_PORT, TEST_HOST);
    } else {
        throw new Error('Server not found');
    }
};

TestServer.prototype.stop = function() {
    var self = this;
    self.server.close();
};

// you would likely want this in another file, and include similar 
// functions for post, put, delete, etc.
function http_get(host, port, url, cb) {
    var options = {
        host: host,
        port: port,
        path: url,
        method: 'GET'
    };
    var ret = false;
    var req = http.request(options, function(res) {
        var buffer = '';
        res.on('data', function(data) {
            buffer += data;
        });
        res.on('end',function(){
            cb(null,buffer);
        });
    });
    req.end();
    req.on('error', function(e) {
        if (!ret) {
            cb(e, null);
        }
    });
}

var client = {
    get: function(url, cb) {
        http_get(TEST_HOST, TEST_PORT, url, cb);
    }
};

var http = {
    server: {
        create: function(router) {
            return new TestServer({router: router});
        }
    },

    client: client
};
module.exports = http;
  • Just realized I missed the point of your question, but maybe this will help anyway. I personally don't test the router functions themselves. I just test via HTTP requests that the server does basically what it's supposed to, then test all the business logic separately since it is all in files outside of the controller anyway. – Bryan Donovan Jan 17 '12 at 02:44
  • You've got a reference to `path/to/some/router` and it would be helpful to see the contents of that file. – ryonlife Mar 26 '12 at 05:31