How To Test Your Express Controllers

You have already seen how to test your models at Testing Your Mongo Models for Node & Express, but your Express controllers are also an integral part of your web application. When they don’t work, your users see 404 and 500 errors, and they might never come back.

This is why your controllers deserve to be tested. No matter what your users throw at them, they should be able to handle it. They are the gate to the internet for your app and people will throw anything at them. You better prepare for it.

However, unlike other parts of your app, your controllers are not independent units. They often depend on a lot of models and exterior services.

This makes them more tricky to test, but there are still ways to do it.

Which are the dependencies of a controller?

The main dependencies of your controllers are the user requests, your models and exterior services.

The user requests may not seem like a dependency but they are, because they are not generated by you but by some exterior force :-)

Your controllers use the models to get data from the database and to apply some business logic. As a result you depend not only on your models but also on your database.

Your exterior services are services which you are connecting to over HTTP. You can own them or they can be unrelated to you like Twitter or Facebook.

How do you test a controller?

As any other unit of your application, your initial goal is to test it independently. However, you saw that each controller can have some dependencies.

The solution to this problem is to mock your dependcies. You have to find a way to control them.

Initial Setup

However, before everything there are two module that I like to use no matter what I am testing, and I am going to use them today, too.

$ npm install mocha should --save-dev

mocha provides a very convenient way to write and run tests.

should gives you a ton of nice methods and properties to verify that the results are what you expect.

You will learn more about them from the tests below. Anytime you need to run the tests in your tests folder, all you should do is run

$ ./node_modules/.bin/mocha tests/**

Mocking Requests

Let’s have a look at the following simple Express controller.

var express = require('express')
  , router = express.Router()

router.get('/hello', function(req, res) {
  res.send('world')
})

router.get('/upper/:word', function(req, res) {
  res.send(req.params.word.toUpperCase())
})

module.exports = router

The only dependencies of this controller are the requests and response objects required by each handler. These are the items that you should mock first when testing your controllers.

node-mocks-http

Let’s welcome node-mocks-http module which will help you with creating request and response objects under for testing your controller.

$ npm install node-mocks-http --save-dev

node-mocks-http can create request and response objects which you can then pass on to your controller for handling. Then you can see what was the result and check if it was what you expected.

Mocking your requests and responses

Here are the tests for the controller that you just saw.

controller = require('../../controllers/welcome')
  , http_mocks = require('node-mocks-http')
  , should = require('should')

function buildResponse() {
  return http_mocks.createResponse({eventEmitter: require('events').EventEmitter})
}

describe('Welcome Controller Tests', function() {

  it('hello', function(done) {
    var response = buildResponse()
    var request  = http_mocks.createRequest({
      method: 'GET',
      url: '/hello',
    })

    response.on('end', function() {
      response._getData().should.equal('world');
      done()
    })

    controller.handle(request, response)
  })

  it('hello fail', function(done) {
    var response = buildResponse()
    var request  = http_mocks.createRequest({
      method: 'POST',
      url: '/hello',
    })

    response.on('end', function() {
      // POST method should not exist.

      // This part of the code should never execute.

      done(new Error("Received a response"))
    })

    controller.handle(request, response, function() {
      done()
    })
  })

  it('upper', function(done) {
    var response = buildResponse()
    var request  = http_mocks.createRequest({
      method: 'GET',
      url: '/upper/monkeys',
    })

    response.on('end', function() {
      response._getData().should.equal('MONKEYS');
      done()
    })

    controller.handle(request, response)
  })
})

Let’s have a closer look at the tests from above.

There is a helper method buildResponse which uses the mock library to build a response object with some default settings. In this case it adds an event emitter.

It is a good practice to always have this event emitter. The only way to know that a controller has finished its work and rendered a response is when it emits the end event. This is very useful when there are asynchronous requests to a database or a web service.

In each test we also create a request object, by providing a url and a method.

Then we wait for the end event to know that the controller has processed the request. This is where you have to call the done function to let know mocha that you have finished testing this piece of code.

Finally, you call the handle method of the controller to dispatch your request, based on the url and the method.

However, the end event is sent only when a proper combination url and method is found. Otherwise, the third and optional argument of the handle method is called.

This is the next function, just like in a middleware because controllers are essentially middlewares.

You can see that we use this in the second test to check that the controller does not handle POST request for the /hello url.

Mocking Models

However, most controllers are a little bit more complex than the one from our first example. They usually at least talk with a model.

Let’s have a look at the following controller.

var express = require('express')
  , router = express.Router()
  , model = require('../models/news')

router.get('/all', function(req, res) {
  model.all(function(err, items) {
    if (err) return res.json({error: "There is a problem"})
    res.json({error: null, news: items})
  })
})

router.post('/create', function(req, res) {
  model.create(req.body.title, req.body.text, function(err, doc) {
    if (err) return res.json({error: "There is a problem"})
    res.json({error: null, news: doc})
  })
})

module.exports = router

In this controller, you are reading from and writing to a database while using a model.

mockery

This is an external dependencies that we would like to control. That is why we are going to use mockery

npm install mockery --save-dev

mockery killer feature is that when a module is loaded with require it can provide a different object instead of the one returned by the module.

This is all done transparently to your code.

To make this work you need to first call mockery.enable() before loading the module and then mockery.disable() when you are done.

Let’s have a look at how it works in the tests of our controller.

var http_mocks = require('node-mocks-http')
  , should = require('should')
  , mockery = require('mockery')

function buildResponse() {
  return http_mocks.createResponse({eventEmitter: require('events').EventEmitter})
}

describe('News Controller Tests', function() {

  before(function() {
    mockery.enable({
      warnOnUnregistered: false
    })

    mockery.registerMock('../models/news', {
      all: (cb) => cb(null, ["First news", "Second news"]),
      create: (title, text, cb) => cb(null, {title:title, text:text, id: Math.random()})
    })

    this.controller = require('../../controllers/news')
  })

  after(function() {
    mockery.disable()
  })

  it('all', function(done) {
    var response = buildResponse()
    var request  = http_mocks.createRequest({
      method: 'GET',
      url: '/all',
    })

    response.on('end', function() {
      response._isJSON().should.be.true

      var data = JSON.parse(response._getData())
      should.not.exist(data.error)
      data.news.length.should.eql(2)
      data.news[0].should.eql("First news")
      data.news[1].should.eql("Second news")

      done()
    })

    this.controller.handle(request, response)
  })

  it('create', function(done) {
    var response = buildResponse()
    var request  = http_mocks.createRequest({
      method: 'POST',
      url: '/create',
    })

    request.body = {
      title: "Something is happening",
      text: "Something is happening in the world!"
    }

    response.on('end', function() {
      response._isJSON().should.be.true

      var data = JSON.parse(response._getData())
      should.not.exist(data.error)
      data.news.title.should.eql(request.body.title)
      data.news.text.should.eql(request.body.text)
      data.news.id.should.exist

      done()
    })

    this.controller.handle(request, response)
  })
})

As you can see, you need to load the controller in the before method which executes exactly once, but not before you enable the mockery module with mockery.enable().

Just after enabling it you have to load the mock object, in this case the mock model.

In the first test, all we do is reading data from the database. However, thanks to the mocking object you have full control on what is returned and you can verify whether your controller returns it correctly.

In the second test, we test what happens when we send some data to a post controller which is supposed to save it. The mock object helps us to check whether the controller tried to save the data.

As you can see mocking models is very easy with mockery. Actually mocking any kind of module is very easy, like an SDK for a framework for example.

Mocking Services

There is one last dependency that you might need to handle. Your app can depend on exterior web services.

Maybe it loads data from Facebook or from some other place. It is important to mock them not only because you need to control what they return during tests, but because real HTTP requests are slow compared to how fast your tests usually run.

nock

Thankfully there is a module named nock which can help with that

$ npm install nock --save-dev

Whenever an http request is made nock intercepts it and responds with whatever you want. You can select the data it sends back, the status code and much more.

Let’s have a look at what your controller might look like.

var express = require('express')
  , router = express.Router()

router.get('/service', function(req, res) {
  request('http://www.google.com', function (error, response, body) {
    if (!error && response.statusCode == 200) {
      res.send(body)
    }
  })
})

module.exports = router

In this simple controller all we do is using the request module to easily load some data from Google’s home page and then return it back.

Let’s have a look how to handle that situation in your tests

var controller = require('../../controllers/services')
  , mocks = require('node-mocks-http')
  , should = require('should')
  , nock = require('nock')


function buildResponse() {
  return http_mocks.createResponse({eventEmitter: require('events').EventEmitter})
}

describe('Services Controller Tests', function() {

  beforeEach(function(done) {
    this.response = mocks.createResponse({eventEmitter: require('events').EventEmitter})
    done()
  })

  it('Service', function(done) {
    nock('http://www.google.com').get('/').reply(200, 'something funny')

    var response = buildResponse()
    var request  = mocks.createRequest({
      method: 'GET',
      url: '/service',
    })

    response.on('end', function() {
      response._getData().should.eql('something funny')
      done()
    })

    controller.handle(request, response)
  })
})

It is very simple to use nock. You just have to tell it which url it should intercept and what it should return.

In this case it always responds with status code 200 (success) and with ‘something funny’. Even when the network is down you are still going to be able to run your tests.

The only thing you should be careful about is that each time you setup nock to handle the request for a specific path, it will handle it just once, and then allow the request next time.

What if I have some other dependencies?

You can probably use the tools that we used above to mock how this module works.

If it is something that you use require to load you can use mockery. If is something over a network, then you would probably use nock.

Next

You have learned how to test your controllers. You can begin applying this knowledge immediately with your simplest controllers and continue with the rest of them.

You can explore what other features mockery, nock and node-mocks-http provide. They have much more to offer than what I just showed you.


Other articles that you may like

Did you like this article?

Please share it

We are Stefan Fidanov & Vasil Lyutskanov. We share actionable advice about development with Node, Express, React and other web & mobile technologies.

It is everything that we have learned from years of experience working with customers from all over the world on projects of all sizes.

Let's work together
© 2024 Terlici Ltd · Terms · Privacy