Testing express-validator?

Just wondering how to test my route validation? I remember being told before that you shouldn't test an external library, but surely I should test my implementation of it? For example:
// routes.js
router.post(
'/someroute',
[
body('email')
.isString()
],
controller.doSomthing);
// routes.js
router.post(
'/someroute',
[
body('email')
.isString()
],
controller.doSomthing);
// controller.js
exports.doSomething = (req, res, next) => {
const errors = validationResult(req);

if(!errors.isEmpty()) {
console.log(errors);
const error = new Error('Validation Error');
error.statusCode = 422;
throw error;
}
};
// controller.js
exports.doSomething = (req, res, next) => {
const errors = validationResult(req);

if(!errors.isEmpty()) {
console.log(errors);
const error = new Error('Validation Error');
error.statusCode = 422;
throw error;
}
};
I should obviously mock/unit test the doSomthing function, but should I be using chai-http to test if I'm using express-validator correctly in routes.js? As a slight aside - I should run integration tests less frequently right? Anything that needs to actually query a db or make an http request seems like it's too time intensive to be running as part of my start script Sorry if these questions seem obvious, it's been a while since I looked at testing, and even then I was new to it!
10 Replies
Joao
Joao2y ago
In this case I would recommend to simply mock the validationResult return value. I don't remember if it's part of the express-validator library? Either way you can "plug" into it and tell it what to return for that particular function. Then you can have two separate tests, where the isEmpty method returns either true or false. Your controller should behave as expected given both conditions, which are computed outside of it, so you just mock the return value for each case.
JWode
JWodeOP2y ago
Hey @joao6246 , yeah, validationResult is part of express-validator. I'm just concerned that the accuracy of what validationResult returns depends on whether I've implemented the body() checks correctly. I can't think of an example right now, but I've definitely had no errors when there should've been, and vice versa. It's probably down to my skill level, but I do often think that I need a testing library more for my tests than I do my actual code. I definitely don't believe mocha straight away when a test passes 😄
Joao
Joao2y ago
Well, that's really not what you are testing for. However validationResult is implemented in the library is not your concern for this test. In this test you are concer with what your code does and you need to explore all the possible scenarios, which in this particular case there are two. So you write one test where you mock the response, simulating a scenario where the validationResult would return true, and another where it would return false. If you have concerns about the library that means either the library itself is unreliable (in which case I would suggest you bring it up to the attention of the developers) or your code simply does not perform as you are expecting. Perhaps because a bug somewhere or didn't fully understand the library. Luckily that's what tests are for: to consistently reproduce the behavior of a particular piece of code. Quick example, don't take it verbatim cause some methods might be off.
test('should throw an error when body fails validation', () => {
const validationResultSpy = vi.spyOn(express, 'validationResult');
validationResultSpy.mockReturnedValueOnce(false);
expect(doSomethingController(mockReq, mockRes, mockNext)).toThrow();
});
test('should throw an error when body fails validation', () => {
const validationResultSpy = vi.spyOn(express, 'validationResult');
validationResultSpy.mockReturnedValueOnce(false);
expect(doSomethingController(mockReq, mockRes, mockNext)).toThrow();
});
This is using vitest which is almost identical to jest, unfortunately I haven't used Mocha
JWode
JWodeOP2y ago
Yeah, it looks familiar to mocha, and I definitely get what you're saying, but I feel like I haven't quite explained where I feel like there's a gap: validationResult is not my concern to test, 100%. But let's say I want to make sure that any email field is a string, is between two lengths, is optional and removes white space. I'd write:
body('email')
.isString()
.isLength({min:4, max:50})
.isEmail()
.optional({checkFalsy})
.trim()
body('email')
.isString()
.isLength({min:4, max:50})
.isEmail()
.optional({checkFalsy})
.trim()
and then that would be used by validationResult . but that code is mine, and I've made mistakes there before (specifically around using functions like .trim() and assuming they're the libraries functions when they aren't). So validationResult no doubt works, but garbage in/garbage out 🤷‍♂️ (ironically I actually have made a mistake in that validation code: {checkFalsy: true}) edit: Here's one I made a mistake on:
body('phone')
.isString()
.replace(/\s*/g,"")
.matches(/^0([1-6][0-9]{8,10}|7[0-9]{9})$/)
.withMessage('Please enter a UK phone number'),
body('phone')
.isString()
.replace(/\s*/g,"")
.matches(/^0([1-6][0-9]{8,10}|7[0-9]{9})$/)
.withMessage('Please enter a UK phone number'),
I expected this to remove whitespaces. But replace is actually an express-validator method, and doesn't work with regex unlike js replace
Joao
Joao2y ago
Ahh ok I get it. So you want to test that the rules that you create, using express-validator, are actually valid. Sorry I think I'm a little dense today 😄 Well in that case what I personally like to do in those cases is create separate classes that run the validation for each thing I'm interested in. So I would wrap that around a function or method and write unit tests for that separately. If that is what you mean... ?
JWode
JWodeOP2y ago
That definitely sounds like what I'm after, but I'm not entirely sure how I'd put that into practice.... So I'd create a class (or function?) that contained the test and then use that as middleware?
exports.emailTest = (input) => {
body(input)
.isString()
.isLength({min:4, max:50})
.isEmail()
.optional({checkFalsy})
.trim()
}
exports.emailTest = (input) => {
body(input)
.isString()
.isLength({min:4, max:50})
.isEmail()
.optional({checkFalsy})
.trim()
}
^^ see I've already realised I'm out of my depth, body takes the name of the field as a string, not the value, so I don't really know how to approach this. Looking at the docs has confirmed just how much I hate the express-validator docs 😄 (can't see how to extract the tests 🤷‍♂️ ) I'm definitely going to have to have more of a think about this, but what you're saying is right - if I could extract that into another testable file that would be ideal I guess I have to look at how body is implemented (ie how it receives the request object)
Joao
Joao2y ago
I've only used express-validator a couple of times for pet projects and I can't remember much about it but it wasn't exactly straight forward to understand. If you need some suggestions you can take a look Joi, Ajv and Zod. But yeah, whatever rules you need just wrap them in a function and place them in a /validators directory somewhere, at least for now just to get things working. So you can have functions like validateLoginInput or something similar that already encapsulates the whole validating logic. And write tests for that separately with the expected results of course.
JWode
JWodeOP2y ago
Yeah, I actually ended up learning Joi for my front end because I was so annoyed with express validator last time.
Think I'll probably stick with it on the backend though and ask SO how I can extract that logic for testing. Thanks though, you've been a great help 👍
Joao
Joao2y ago
If you move on to TypeScript I definitely recommend zod, but that's a topic for another day
JWode
JWodeOP2y ago
Running validations imperatively | express-validator
express-validator favors the declarative way of doing things that express middlewares bring.
Want results from more Discord servers?
Add your server