Carasel
Carasel

Reputation: 2871

Unit testing validation with express-validator

How can I unit test my validations that are done using express-validator?

I have tried creating a dummy request object, but I get the error: TypeError: Object #<Object> has no method 'checkBody'. I am able to manually test that the validation works in the application.

Here is what I have tried:

describe('couponModel', function () {
    it('returns errors when necessary fields are empty', function(done){
        var testBody = {
            merchant : '',
            startDate : '',
            endDate : ''
        };
        var request = {
            body : testBody
        };
        var errors = Model.validateCouponForm(request);
        errors.should.not.be.empty;
        done();
    });
});

My understanding is that the checkBody method is added to the request object when I have app.use(expressValidator()) in my express application, but as I am only testing that the validation is working in this unit test I do not have an instance of the express application available, and the validation method that I am testing is not called directly from it anyway as it is only called through a post route, which I do not want to call for a unit test as it involves a database operation.

Upvotes: 9

Views: 6269

Answers (2)

achalk
achalk

Reputation: 3449

Here's a solution for the new express-validator api (v4):

tl;dr: You can use this function:

exports.testExpressValidatorMiddleware = async (req, res, middlewares) => {
  await Promise.all(middlewares.map(async (middleware) => {
    await middleware(req, res, () => undefined);
  }));
};

It can be called like this:

const { validationResult } = require('express-validator/check');

await testExpressValidatorMiddleware(req, res, expressValidatorMiddlewareArray);
const result = validationResult(req);
expect(result....

These solutions assume you have the async/await syntax available. You can use the node-mocks-http library to create the req and res objects.

Explanation:

Each element in an express-validator array is applied to the route as middleware. Say this is your array:

[
  check('addresses.*.street').exists(),
  check('addresses.*.postalCode').isPostalCode(),
]

Each check will be loaded as middleware.

In order to test middleware, we need to implement a function which acts similarly to how express implements middleware.

Express middleware always accepts three params, the request and response objects, and the next function it should call (next by convention). Why do we need next? For scenarios where we want our middleware to do something before and after the proceeding function, e.g.

const loggerMiddleware = (req, res, next) => {
  console.log('req body is ' + req.body);
  next();
  console.log('res status is ' + res.status);
};

But express-validator doesn't do this, it just calls next() once each of its validators is finished. For that reason, our implementation doesn't really need to bother with next().

Instead, we can just run each of our middlewares in turn and pass an empty function as next to avoid a TypeError:

middlewares.map((middleware) => {
  middleware(req, res, () => undefined);
});

But this won't work, because express-validator middleware returns promises and we need to wait for them to resolve...

middlewares.map(async (middleware) => {
  await middleware(req, res, () => undefined);
});

And we don't want to move on until all promises in our iteration are resolved (Mozilla docs on Promise.all are here):

await Promise.all(middlewares.map(async (middleware) => {
  await middleware(req, res, () => undefined);
}));

And we should extract this as a reusable function:

exports.testExpressValidatorMiddleware = async (req, res, middlewares) => {
  await Promise.all(middlewares.map(async (middleware) => {
    await middleware(req, res, () => undefined);
  }));
};

And now we've arrived at my solution. If someone can improve on this implementation, I'm very happy to make edits.

Upvotes: 9

marc_aragones
marc_aragones

Reputation: 4474

I faced the same issue and I had to create the methods using this:

var validRequest = {
  // Default validations used
  checkBody: function () { return this; },
  checkQuery: function () { return this; },
  notEmpty: function () { return this; },

  // Custom validations used
  isArray: function () { return this; },
  gte: function () { return this; },

  // Validation errors
  validationErrors: function () { return false; }
};

function getValidInputRequest(request) {
    Object.assign(request, validRequest);
    return request;
}

So, in your code you have to call the getValidInputRequest helper:

describe('couponModel', function () {
  it('returns errors when necessary fields are empty', function(done){
      var testBody = {
          merchant : '',
          startDate : '',
          endDate : ''
      };
      var request = {
          body : testBody
      };

      request = getValidInputRequest(request); // <-- Update the request

      var errors = Model.validateCouponForm(request);
      errors.should.not.be.empty;
      done();
  });
});

Now, the request object has the body property and all the methods needed by express-validator.

If you want to test the cases that the validator fails, you should use something like this:

function getInvalidInputRequest(request, errorParams) {
    // Get de default valid request
    Object.assign(request, validRequest);

    // Override the validationErrors function with desired errors
    request.validationErrors = function () {
        var errors = [];
        errorParams.forEach(function(error){
            errors.push({msg: 'the parameter "'+ error +'" is mandatory'})
        });
        return errors;
    };
    return request;
}

And to update the request you should do:

request = getInvalidInputRequest(request, ['mandatory_param_1', 'mandatory_param_2']);

Upvotes: 1

Related Questions