I'm pretty new to node and this is my first time unit testing an app. I'm doing well with Jest faking the request with Jest function as below
// Create a fake request
const mockRequest = (sessionData, body) => ({
session: { data: sessionData },
body
});
// Create a fake response
const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
const mockNext = () => {
const next = jest.fn();
return next;
};
So I can use them like follows
doSomething(req, res, next);
expect(res.status).toHaveBeenCalledWith(201);
//or
expect(next).toHaveBeenCalled();
That's enough for all the cases until I found that my authorisation middleware includes a couple of parameters so I can not pass the fake res and req as below
exports.isAllowedTo = (par1, par2) => {
return async (req, res, next) => {
try {
//
// Grant logic here that needs par1 and par2
//
if(granted)
next();
else
return res.status(401).json({
error: "You don't have enough permission to perform this action"
});
} catch (err) {
res.status(406).json({
error: err.toString(),
})
}
}
}
If I test isAllowTo(req, res, next) with the mock req, res and next then I'm missing the 2 parameters needed by the function. Actually when I do this, the function isAllowTo() is not even called. I don't know how to deal with that. Any suggestion or approach?
Two months later I realized that the real problem is that I'm testing a function inside of another function.
So firstly I store the function in a variable so I can test it as a regular middleware.
test('Grant access if user role is allowed to', async () => {
const isAllowToTester = userController.isAllowedTo(par1, par2);
await isAllowToTester(req, res, next)
expect(next).toHaveBeenCalled();
});
Hope this helps someone else.
Credits to this post
Check out https://github.com/nock/nock it's a library dedicated to mocking requests and responses, it's really easy to use with unit tests/jest. I personally don't think is worth it to write your own mocking implementation.
Related
I am trying to append some data to my request object using a middleware, but I want to do it only once the server is up.
So I tried doing it with a middleware, while trying to use a function's context, but it's a bit problematic to perform such an action on a middleware, because I cannot pass a promise as a middleware.
This is what I'm trying to do:
const setupData = async () => {
const data = await getSomeData();
return (req, res, next) => {
req.data = data;
next();
}
}
app.use(setupData());
I tried using the solution suggested here, but it won't work as this will happen on every request.
Any idea how can I go around this? I can always put the info on a global var, but I would like to avoid it.
I also saw some in-memory packages to help with it (such as node-cache), but I would like to do it with a middleware.
Thanks in advance
Just cache the result using a normal variable:
let data = null;
function setupData (req, res, next) {
if (data !== null) {
req.data = data;
next();
}
else {
getSomeData().then(result => {
data = result
req.data = data;
next();
});
}
}
app.use(setupData);
This is the minimal, least complicated implementation. You can of course refactor it to be much DRYer and less prone to mistakes by taking out the caching logic:
Cleaner Implementation
let cache = null;
async function getCachedData() {
if (cache === null) {
cache = await getSomeData();
}
return cache;
}
Which makes setupData much cleaner:
function setupData (req, res, next) {
getCachedData().then(data => {
req.data = data;
next();
});
}
Either way, the cache is triggered on the first request. This means that there is a possibility that a second request may arrive before the data is possibly cached. So at startup the getSomeData() function may run more than once.
Really call getSomeData() ONLY ONCE
If you really want to call getSomeData only once you must call it before setting up Express:
async function main () {
const data = await getSomeData();
const app = express();
//
// set up express middlewares...
//
app.use((req,res,next) => {
req.data = data;
next();
});
//
// set up routes...
//
app.listen(config.port);
}
main(); // start everything
The key here is to realize that we have been trying to do everything backwards: to set up a constant value asynchronously AFTER starting to set up Express. The natural flow of the program wants the constant value to exist BEFORE we begin setting up Express so we only perform everything else inside the async function (here called main). Not try to run the async function while setting up Express.
You can do it without async:-
const setupData = (req, res, next) => {
// You can put a condition here so that it runs only once
getSomeData().then((data) => {
req.app.locals.data = data //The data can be accessed in the next middleware using req.app.locals
next();
}).catch((error) => {
console.log("Error Occured");
res.status(400).end("Error Occurred");
})
}
app.use(setupData);
You should see the documentation of getSomeData and see how it works
I want to create a middleware that automatically formats my output and returns it in a format that looks like
{
"successful": "true",
"message": "Successfully created",
"data": {
"name": "Joe",
"year": 1
}
}
Currently I'm just returning a json of the data itself (name, year, etc.)
I want to add the "successful", "message", etc.
Here's some snippets of my code below:
routes/student.js
var student_controller = require('../controllers/studentController');
router.get('/list', student_controller.student_list);
controllers/student.js
var Student = require('../models/student');
exports.student_list = function(req, res, next) {
Student.find()
.exec(function(err, list_students) {
if (err) {return next(err);}
res.json(list_students);
});
};
app.js
var studentRouter = require('./routes/student');
app.use('/student', studentRouter);
How do I make this middleware, and in which file(s) should I be calling it?
The response can be intercepted by overriding response.json function. By doing so, and adding our custom function, every time, response.json() is called, our intercepting function is triggered.
middleware/response.filter.js:
// Response Interceptor Middleware
export default (request, response, next) => {
try {
const oldJSON = response.json;
response.json = (data) => {
// For Async call, handle the promise and then set the data to `oldJson`
if (data && data.then != undefined) {
// Resetting json to original to avoid cyclic call.
return data.then((responseData) => {
// Custom logic/code. -----> Write your logic to add success wrapper around the response
response.json = oldJSON;
return oldJSON.call(response, responseData);
}).catch((error) => {
next(error);
});
} else {
// For non-async interceptor functions
// Resetting json to original to avoid cyclic call.
// Custom logic/code.
response.json = oldJSON;
return oldJSON.call(response, finalResponse);
}
}
} catch (error) {
next(error);
}
}
In the Server.js file, register the middleware:
// Server.js file
import externalResponseFilter from "./middleware/response.filter.js:";
// Create Express server
const app = express();
// Response interceptor - Initialization.
app.use(externalResponseFilter);
And in the controller where you are returning the response, return with response.json() function instead of response.send().
Let me know if any additional explanation is required.
I think there is no need to middleware in your case unless you need an auth check for this routing. The Middleware has the functionality to handle something before getting your response.
your code shall be:
router.get('/list',(req,res,next)=>{
Student.find().then(data=>res.json(data)).catch(err=>conole.log(err)) });
I use Supertest to test my Express apps, but I'm running into a challenge when I want my handlers to do asynchronous processing after a request is sent. Take this code, for example:
const request = require('supertest');
const express = require('express');
const app = express();
app.get('/user', async (req, res) => {
res.status(200).json({ success: true });
await someAsyncTaskThatHappensAfterTheResponse();
});
describe('A Simple Test', () => {
it('should get a valid response', () => {
return request(app)
.get('/user')
.expect(200)
.then(response => {
// Test stuff here.
});
});
});
If the someAsyncTaskThatHappensAfterTheResponse() call throws an error, then the test here is subject to a race condition where it may or may not failed based on that error. Even aside from error handling, it's also difficult to check for side effects if they happen after the response is set. Imagine that you wanted to trigger database updates after sending a response. You wouldn't be able to tell from your test when you should expect that the updates have completely. Is there any way to use Supertest to wait until the handler function has finished executing?
This can not be done easily because supertest acts like a client and you do not have access to the actual req/res objects in express (see https://stackoverflow.com/a/26811414/387094).
As a complete hacky workaround, here is what worked for me.
Create a file which house a callback/promise. For instance, my file test-hack.js looks like so:
let callback = null
export const callbackPromise = () => new Promise((resolve) => {
callback = resolve
})
export default function callWhenComplete () {
if (callback) callback('hack complete')
}
When all processing is complete, call the callback callWhenComplete function. For instance, my middleware looks like so.
import callWhenComplete from './test-hack'
export default function middlewareIpnMyo () {
return async function route (req, res, next) {
res.status(200)
res.send()
// async logic logic
callWhenComplete()
}
}
And finally in your test, await for the callbackPromise like so:
import { callbackPromise } from 'test-hack'
describe('POST /someHack', () => {
it.only('should handle a post request', async () => {
const response = await request
.post('/someHack')
.send({soMuch: 'hackery'})
.expect(200)
const result = await callbackPromise()
// anything below this is executed after callWhenComplete() is
// executed from the route
})
})
Inspired by #travis-stevens, here is a slightly different solution that uses setInterval so you can be sure the promise is set up before you make your supertest call. This also allows tracking requests by id in case you want to use the library for many tests without collisions.
const backgroundResult = {};
export function backgroundListener(id, ms = 1000) {
backgroundResult[id] = false;
return new Promise(resolve => {
// set up interval
const interval = setInterval(isComplete, ms);
// completion logic
function isComplete() {
if (false !== backgroundResult[id]) {
resolve(backgroundResult[id]);
delete backgroundResult[id];
clearInterval(interval);
}
}
});
}
export function backgroundComplete(id, result = true) {
if (id in backgroundResult) {
backgroundResult[id] = result;
}
}
Make a call to get the listener promise BEFORE your supertest.request() call (in this case, using agent).
it('should respond with a 200 but background error for failed async', async function() {
const agent = supertest.agent(app);
const trackingId = 'jds934894d34kdkd';
const bgListener = background.backgroundListener(trackingId);
// post something but include tracking id
await agent
.post('/v1/user')
.field('testTrackingId', trackingId)
.field('name', 'Bob Smith')
.expect(200);
// execute the promise which waits for the completion function to run
const backgroundError = await bgListener;
// should have received an error
assert.equal(backgroundError instanceof Error, true);
});
Your controller should expect the tracking id and pass it to the complete function at the end of controller backgrounded processing. Passing an error as the second value is one way to check the result later, but you can just pass false or whatever you like.
// if background task(s) were successful, promise in test will return true
backgroundComplete(testTrackingId);
// if not successful, promise in test will return this error object
backgroundComplete(testTrackingId, new Error('Failed'));
If anyone has any comments or improvements, that would be appreciated :)
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.
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.
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']);
I am having trouble trying to write a Mocha test for an if else statement in Node.js. Here is what is being tested:
exports.restrict = function(req, res, next) {
if (req.session.user) {
next();
} else {
req.session.error = 'Access denied!';
res.redirect('/login');
}
}
This is how I am trying to test it:
describe('interface', function(){
it('should show ability to asynchronously deny a user with an incorrect password', function(done){
function restrict (req, res, next){
if (req.session.user) {
next();
done();
} else {
req.session.error.should.equals('Access denied');
req.redirect.should.equals('/login');
done();
}
}
});
});
The error I receive when running the test is that it say timeout because it exceeds 2000ms. What am I doing wrong?
In your test you define a restrict function which is never called, so done is never called and the test times out. This is what you are doing wrong. There's nothing special to using if... else in Mocha tests.
Wider issue: what you show in your question suggests that you duplicate the code of your module into your test suite. That's a terrible way to go about testing code. You should structure your module so that your test suite is able to exercise its functions without duplication of code.
Yes, there was a incorrect statement in your test file. Reading your source file, we have two scenarios to check for restrict function.
if user session exist, check if next() is called
if user session not exist, check if redirect() is called and req.session.error is assigned
Based on top bullets, we can design our test such as
const sinon = require('sinon');
const chai = require('chai');
const assert = chai.assert;
const src = require('./src');
describe('interface', function () {
it('calls next() if user session exist', function () {
const next = sinon.spy();
const res = sinon.spy();
const req = {
session: {
user: 'username' // set user session
}
}
src.restrict(req, res, next);
assert(next.called);
});
it('redirects if user session not exist', function () {
const next = sinon.spy();
const res = {
redirect: sinon.spy()
}
const req = {
session: {}
}
src.restrict(req, res, next);
assert.equal(req.session.error, 'Access denied!');
assert(res.redirect.calledWith('/login'));
assert.isFalse(next.called)
});
});
we don't need to use done() because restrict() is not async function.
We also use sinon here to spy req, res and next variable.
Ref:
- https://sinonjs.org/
Hope it helps.