Unit-testing – Unit test express controllers

unit testing

I'm using meanjs for a project. It includes a yeoman generator with some express tests (model.test.js & routes.test.js)

The tests do exactly what they advertise. My question is though, should I be writing tests for my express controllers as well?

Right now the routes tests make sure the server responds with the appropriate data as a whole, but should I be writing more granular tests for each part of my controller as well? I don't see many examples of this online….

Best Answer

TLDR

There are two options:

  1. Continue testing as a whole with functional tests (because these as a whole tests are not unit tests). You can feel this is not the right way, but it can be the only way to go with your current code base.
  2. Refactor code to smaller units (classes / functions) to make real unit testing possible. Then create unit tests for these smaller "units".

Long Explanation

First, to make sure we are talking about the same, the example of the controller code:

// Controller code
exports.changePassword = function (req, res, next) {
  // Init Variables
  var passwordDetails = req.body;
  var message = null;

  if (req.user) {
    if (passwordDetails.newPassword) {
      User.findById(req.user.id, function (err, user) {
        if (!err && user) {
          if (user.authenticate(passwordDetails.currentPassword)) {
            if (passwordDetails.newPassword === passwordDetails.verifyPassword) {
              user.password = passwordDetails.newPassword;

              user.save(function (err) {
                  ...
                  res.send({ message: 'Password changed successfully' });
              });
            } else {
              res.status(400).send({ message: 'Passwords do not match' });
            }
          } else { 
              res.status(400).send({ message: 'Current password is incorrect' }); 
          }
        } else {
          res.status(400).send({ message: 'User is not found' });
        }
      });
    } else {
      res.status(400).send({ message: 'Please provide a new password' });
    }
  } else {
    res.status(400).send({ message: 'User is not signed in' });
  }
};

And the test code:

// Change password
...
// agent performs the http request to the application
agent.post('/api/users/password')
  .send({
    newPassword: '1234567890Aa$', ...
  })
  .expect(200)
  .end(function (err, res) {
    ...
    res.body.message.should.equal('Password changed successfully');
    return done();
  });

This approach works, but these tests are actually not unit tests, but functional tests which verify the system as a black-box. That means they are slower and less granular.

So how can we write unit tests for the controllers? With the existing structure it can be done like this (this is not a real code, just to demonstrate the idea):

var users = require('../controllers/users.server.controller');

describe('User Controller Unit Tests:', function () {

  describe('Change Password', function () {
    it('should change the password', function (done) {
      req = RequestMock({
          'body': {
              'newPassword': 'xxx', 'passwordVerify': 'xxx'
          }
      );
      res = ResponseMock();
      users.changePassword(req, res).then(function() {
        res.body.message.should.equal('Password changed successfully');
      });
    });

We get closer to the unit test and now we have request / response mocks and call our changePassword directly, but we still test it as a whole. The test structure didn't change much.

The solution is to make the code testable first, re-factor it to extract the units from the changePassword code, which now does too much:

  • Get the incoming data from the request
  • Validate incoming data
  • Change the password
  • Handle possible errors
  • Format the output
  • Send the output

The better code structure could contain following objects:

  • Controller - get the incoming data from the request, format and send the output
  • NewPasswordConstraints - validate / verify incoming data
  • User - change the password

The controller code could be something like this:

// Controller code
exports.changePassword = function (req, res, next) {
  // Get the incoming data
  var passwordDetails = req.body;
  // Validate the incoming data
  NewPasswordConstraints(passwordDetails).validate().then(function(user) {
    // Actually change the password
    return user.set_new_password(passwordDetails.newPassword);
  }).then(function(user) {
    // Format and send the success response
    res.send({ message: 'Password changed successfully' }); 
    // Format and send the error response
  }).catch(function(error) {
    res.status(error.code).send({ message: error.message }); 
  });
}

Again, this is more a pseudo-code than real code, but I think the idea should be clear.

Now you can create unit tests for the business logic - NewPasswordConstraints and User classes.

At the same time, controller code becomes slim and only integrates other objects. I usually don't write unit tests for these 'slim' controllers and leave them for the functional tests.

Related Topic