4

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....

1 Answer 1

4

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.

4
  • What do you do to test that routes call the correct controller? Are those the "funtional tests" that you mention? And are the functional tests written using the same test framework? Nov 27, 2017 at 17:30
  • By "functional tests" I mean the tests that do something like agent.post('/api/users/password').send(...).expect(...).end(...) - in this case you naturally test everything together, including the fact that the route calls the correct controller - this is what I start the answer with. The advantage of going deeper and having smaller testable "units" and "unit tests" is that tests are more focused, it's easier to test all the logic and the tests are faster because you don't have to go through the full request-response cycle. Nov 28, 2017 at 10:05
  • Great, thanks. We're having a similar issue with testing in our project, not knowing how much to do at which level. Would you normally handle the edge cases at unit level, and then do one or two normal paths at the functional level? Nov 29, 2017 at 21:07
  • Yes, if you have "units" (functions / classes) that you can test independently, you can easily cover all the code paths with small and fast unit tests. Then you can have few high-level tests that test everything together. Nov 30, 2017 at 20:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.