40

My application has several layers: middleware, controllers, managers. Controllers interface is identical to middlewares one: (req, res, next).

So my question is: how can I test my controllers without starting the server and sending 'real' requests to localhost. What I want to do is to create request, response instances as nodejs does and then just call controllers method.

Something like this:

var req = new Request()
var res = new Response()
var next = function(err) {console.log('lala')}
controller.get_user(req, res, next)

Any advice is highly appreciated. Thanks!

P.S. the reason why I want to do this is that at the end I would like to test whether the response object contains correct variables for the jade views.

Mark
  • 90,562
  • 7
  • 108
  • 148
potomok
  • 1,249
  • 3
  • 12
  • 16
  • Check a new module I just made: [test-controller](https://github.com/franciscop/test-controller) for mocking and performing requests based on dupertest – Francisco Presencia Nov 02 '15 at 20:37

6 Answers6

25

There's a semi decent implementation at node-mocks-http

Require it:

var mocks = require('node-mocks-http');

you can then compose req and response objects:

req = mocks.createRequest();
res = mocks.createResponse();

You can then test your controller directly:

var demoController = require('demoController');
demoController.login(req, res);

assert.equal(res.json, {})

caveat

There is at time of writing an issue in this implementation to do with the event emitter not being fired.

superluminary
  • 47,086
  • 25
  • 151
  • 148
11

Since JavaScript is a dynamically typed language you can create mock objects and passing them to your controllers as follow:

var req = {};
var res = {};
var next = function(err) {console.log('lala')}
controller.get_user(req, res, next)

If your controller needs a particular piece of data or functionality from your request or response object you'll need to provide such data or functionality in your mocks. For example,

var req = {};
req.url = "http://google.com"; // fake the Url

var res = {};
res.write = function(chunk, encoding) { 
  // fake the write method 
};

var next = function(err) {console.log('lala')}
controller.get_user(req, res, next)
Hector Correa
  • 26,290
  • 8
  • 57
  • 73
  • 7
    well yeh, i understand that, however i will need to write really a big chunk of code for req.body, req.param etc. So it would be much easier just to create an instance of real request object and rewrite some methods – potomok Jan 24 '13 at 09:56
  • Maybe to try `sinon`? – Vladimir Vukanac Jan 29 '19 at 22:47
5

I would try using dupertest for this. It's a node module I created for the very purpose of easy controller testing without having to spin up a new server.

It keeps the familiar syntax of node modules like request or supertest, but again, without the need to spin up a server.

It runs a lot like Hector suggested above, but integrates with a test framework like Jasmine to feel a little more seamless.

An example relating to your question may look like:

request(controller.get_user)
  .params({id: user_id})
  .expect(user, done);

Or the more explicit longhand version:

request(controller.get_user)
  .params({id: user_id})
  .end(function(response) {
    expect(response).toEqual(user);
    done();
  });

Note: the examples assume user_id and user are defined somewhere, and that the controller grabs and returns a user based on id.

Edit: reading your response to an answer above, I will admit the downside currently is that this module does not integrate a more robust mock request or response object by default. dupertest makes it super easy to extend and add properties to both req and res, but by default they are pretty bare.

tydotg
  • 631
  • 4
  • 11
4

If you want to use the real req and res objects, you have to send real requests to the server. However this is much easier than you might think. There are a lot of examples at the express github repo. The following shows the tests for req.route

var express = require('../')
  , request = require('./support/http');

describe('req', function(){
  describe('.route', function(){
    it('should be the executed Route', function(done){
      var app = express();

      app.get('/user/:id/edit', function(req, res){

        // test your controllers with req,res here (like below)

        req.route.method.should.equal('get');
        req.route.path.should.equal('/user/:id/edit');
        res.end();
      });

      request(app)
      .get('/user/12/edit')
      .expect(200, done);
    })
  })
})
zemirco
  • 16,171
  • 8
  • 62
  • 96
  • Quick question - is this a Jasmine test, or is this something else that's included with Node? I'm looking for a solution myself. Thanks! –  Jul 03 '13 at 02:59
  • That is super test https://github.com/visionmedia/supertest. A key part of this set up is you need to separate the creation of your app from the running of the server. Supertest will take care of spinning up the server if necessary and making the request. – Douglas Drouillard Jul 21 '13 at 05:22
4

A bit old post, but I would like to give my 2 cents. The approach you want to take depends on whether you are doing unit testing or integration testing. If you are going down the route of using supertest, that means you are running the actual implementation code and that means you are doing integration testing. If that's what you want to do this approach is fine.

But if you are doing unit testing, you would mock req and res objects (and any other dependencies involved). In the below code (non-relevant code removed for brevity), I am mocking res and giving just a mock implementation of json method, as that's the only method I need for my tests.

// SUT
kids.index = function (req, res) {
if (!req.user || !req.user._id) {
    res.json({
        err: "Invalid request."
    });
} else {
    // non-relevent code
}
};

// Unit test
var req, res, err, sentData;
describe('index', function () {

    beforeEach(function () {
        res = {
            json: function (resp) {
                err = resp.err;
                sentData = resp.kids;
            }
        };
    });
    it("should return error if no user passed in request", function () {
        req = {};

        kidsController.index(req, res);
        expect(err).to.equal("Invalid request.");

    });

    /// More tests....
})
Abhishek Jain
  • 2,957
  • 23
  • 35
0

Take a look at node-tdd and the useNock flag. It builds on top of mocha and nock and automatically creates and uses a recording file for each test.

We love that it's so easy to use. Basically just "enable and forget" and focus on writing requests / test cases. If requests for a test change, one still needs to delete or adjust the recording file, but at least it's entirely separate from the code.

vincent
  • 1,953
  • 3
  • 18
  • 24