Mocha, how to test async code without UnhandledPromiseRejectionWarning - node.js

I'm testing my server-side api endpoints with mochajs and I can't figure out how to do it properly.
I started with code that had the following logic:
it('test', (doneFn) => {
// Add request handler
express.get('/test', (req, res, next) => {
// Send response
res.status(200).end();
// Run some more tests (which will fail and throw an Error)
true.should.be.false;
// And that's the problem, normally my framework would catch the
// error and return it in the response, but that logic can't work
// for code executed after the response is sent.
});
// Launch request
requests.get(url('/test'), (err, resp, body) => { // Handle response
// I need to run some more tests here
true.should.be.true;
// Tell mocha test is finished
doneFn();
});
});
But the test doesn't fail because it throws in the request handling callback.
So I googled around and found that my problem could be solved using promises, and it does, now the test fails. This is the resulting code:
it('test', (doneFn) => {
let handlerPromise;
// Add request handler
express.get('/test', (req, res, next) => {
// Store it in a promise
handlerPromise = new Promise(fulfill => {
res.status(200).end();
true.should.be.false; // Fail
fulfill();
});
});
// Send request
requests.get(url('/test'), (err, resp, body) => {
// Run the other tests
true.should.be.true;
handlerPromise
.then(() => doneFn()) // If no error, pass
.catch(doneFn); // Else, call doneFn(error);
});
});
But now I end up with a deprecation warning because the error is handled in a different process tick than the one it was thrown.
The errors are: UnhandledPromiseRejectionWarning and PromiseRejectionHandledWarning
How can I make my test fail after the response is sent, and avoid having an
unhandledPromiseRejectionWarning?

This works
it('test', (doneFn) => {
let bindRequestHandler = new Promise((reslove, reject) => {
// Add request handler
app.testRouter.get('/test', (req, res, next) => {
// Send response
res.status(200).end();
try { // Here, we need a try/catch/reject logic because we're in a callback (not in the promise scope)
// Run some more tests (which will fail and throw an Error)
true.should.be.false;
} catch (error) { // Catch the failing test errors and reject them
reject(error);
}
resolve();
});
});
let sendRequest = new Promise((reslove, reject) => {
// Launch request
requests.get(url('/test'), (err, resp, body) => { // Handle response
try {
// I need to run some more tests here
true.should.be.true;
} catch (error) { // Catch the failing test errors and reject them
reject(error);
}
reslove();
});
});
Promise.all([bindRequestHandler, sendRequest])
.then(res => doneFn())
.catch(doneFn);
});

Related

Serving a GET request with nested callbacks

I have a generic Node+Express server where I serve GET requests. Some of these GET requests need multiple DB queries which are callbacks.
Here is an example of my code:
GET router:
router.get('/getbalance', function(req, res, next) {
wallet.createNewAddress()
.then(result => {
res.send(result);
})
.catch(err => {
console.log(err);
});
This is the function with callbacks:
async createNewAddress()
{
pool.query(`SELECT ...`)
.then (dbres1 => {
pool.query(`SELECT ...`)
.then(dbres2 => {
(async() => {
var pubkeys = await this.getPublicKeysFromIndexes(wallet.id, index_wallet_1, index_wallet_2, index_wallet_3);
var script = this.generateScript(pubkey1, pubkey2, pubkey3);
})();
})
.catch(e => {
console.log(e.stack);
})
}
})
.catch(e => {
console.log(e.stack);
});
}
I have removed long statements for brevity.
As you can see, I have multiple levels of nested promises.
What is the proper way to handle a request like this? Should I return each promise or should I run everything synchronously using async()?
What I need to do is to return the script at the very middle of the statements. This last call that returns the script is a normal synchronous function.
Appreciate any advice.
Thank you.
I believe using async/await will give you much more readable code, while essentially following the same logic. Of course you will have to be aware that you'll need to add try/catch handler(s) to the code.
If you use async/await you'll end up with something like this:
async function createNewAddress()
{
try {
let dbres1 = await pool.query(`SELECT ...`);
let dbres2 = await pool.query(`SELECT ...`);
var pubkeys = await this.getPublicKeysFromIndexes(wallet.id, index_wallet_1, index_wallet_2, index_wallet_3);
return this.generateScript(pubkey1, pubkey2, pubkey3);;
} catch (err) {
// ok something bad happened.. we could skip this handler and let the error bubble up to the top level handler if we're happy with that approach.
console.error(err);
// Rethrow or create new error here.. we don't want to swallow this.
throw err;
}
}
You can then call as before:
router.get('/getbalance', function(req, res, next) {
wallet.createNewAddress()
.then(result => {
res.send(result);
})
.catch(err => {
console.log(err);
});
Or use an async handler:
router.get('/getbalance', async function(req, res, next) {
try {
let result = await wallet.createNewAddress();
res.send(result);
} catch (err) {
// Also consider sending something back to the client, e.g. 500 error
console.log(err);
};
})

Node JS Promise Rejection Exception

Getting an exception in my Node JS Express application and cannot figure out why. I was getting the same error with my "real" code, so I found this code online and made a test router to see if the error occurred with known good code. Code runs fine in plnkr without the router.post line. Could this be because of the function in the first line? Any help is greatly appreciated.
router.post('/addTableTest', (function (req, res, next) {
let promise1 = new Promise((resolve, reject) => {
let data = false;
if (data) {
resolve('Data');
}
if (!data) {
reject('Not Data');
}
})
promise1.then((message) => {
console.log(message);
}).catch((message) => {
console.log(message);
})
}));
The closure executed by new Promise() is executed synchronously, i.e. immediately after the Promise has been created and before new returns. As your closure has been written to fail immediately and you can't attach a .catch() to it before new returns, you get an unhandled Promise rejection exception.
To make your code work you need to
start a Promise chain by creating a resolved Promise
attach a .then() clause to wrap your synchronous code
replace resolve(X) with return X
replace reject(X) with throw new Error(X)
Now you can safely attach the other Promise clauses, because the code in the just created Promise chain won't be executed until the closure that has created it leaves.
router.post('/addTableTest', (req, res, next) => {
let promise1 = Promise.resolve()
.then(() => {
let data = false; // i.e. the promise will reject
if (data) {
return 'Data';
} else {
throw new Error('Not Data');
}
});
promise1
.then(message => {
console.log(message);
})
.catch(error => {
console.log(error.message);
});
});

Express: send response after getting another response via an api call

I want my website to respond after a successful api call, which is initiated via a post request and also changes some database values.
If there was NO post request the site should also load as usual.
If I do something like this, then the site is getting loaded as usual and then I get an error because of the second rendering attempt.
I guess because node does wait for the receipt, but in parallel does already execute the loadNewSite() function:
app.all('/customer', function(req, res) {
if (Object.keys(req.body).length != 0) {
apiCall(someParameter)
.on('error', error => {console.log(error);} )
.on('receipt', function() {loadNewSite();} );
}
function loadNewSite() {
return res.render('site.html');
}
loadNewSite()
})
try removing the last loadNewSite() as one is already called when you on reciept
check with req.method whether it's a POST request or not.
app.all('/customer', function(req, res) {
// if method is not post handle seperately
if(req.method != 'POST'){
return loadNewSite('site.html');
}
if (Object.keys(req.body).length != 0) {
apiCall(someParameter)
.on('error', error => {console.log(error);} )
.on('receipt', function() {loadNewSite();} );
}
function loadNewSite() {
return res.render('site.html');
}
})
I would create a promise to execute the Api call and resolve it on receipt or reject it on error. Then make the callback async and await for the api call promise.
I left the final call to loadNewSite in case of error, obviously you can modify it and make a function that maybe returns something different in error case.
const execApiCall = (params) => {
return new Promise((resolve, reject) => {
apiCall(params)
.on('error', error => {reject(error);} )
.on('receipt', function() {resolve();} );
})
};
app.all('/customer', async function(req, res) {
function loadNewSite() {
return res.render('site.html');
}
if (Object.keys(req.body).length != 0) {
try {
await execApiCall(params);
return loadNewSite();
} catch (e) { //handle errors }
}
loadNewSite()
})

Mocha tests not failing when they should

I'm trying to test my routes file, and mocha is returning success for all of my expects, even though I've coded a couple that should absolutely fail. I added a 2+2 = 5 test just to make sure something would fail. I have done() in my assertion blocks.
I'm using a MEAN stack, and I tried to test the node files with jasmine, since I'm already using that to test the Angular files, but got tons of crazy errors, so I threw all that out and decided to give mocha a try instead.
Results:
Routes
1) makes sure something fails
GET /
√ returns status code 200
GET /nonexistent
√ returns status code 400
GET /api/todos
√ returns status code 200
√ returns a list of todos
Test file
// test/routes.spec.js
var request = require('request');
var expect = require('chai').expect;
describe('Routes', function() {
var base_url = "http://localhost:8080/"
// does fail as expected
it("makes sure something fails", function () {
expect(2 + 2).to.equal(5);
});
describe("GET /", function() {
it("returns status code 200", function() {
request(base_url, function(error, response, body) {
expect(response.statusCode).to.equal(200);
done();
});
});
});
//should fail
describe("GET /nonexistent", function() {
it("returns status code 400", function () {
request(base_url + "/nonexistent", function (error, response, body) {
expect(response.statusCode).to.equal(200);
done();
});
});
});
describe("GET /api/todos", function() {
it("returns status code 200", function() {
request(base_url + "/api/todos", function(error, response, body) {
expect(response.statusCode).to.equal(200);
done();
});
});
//should fail
it("returns a list of todos", function() {
request(base_url + "/api/todos", function(error, response, body) {
console.log(body);
expect(body).to.equal("abcd");
done();
});
});
});
});
Routes file:
// app/routes.js
var Todo = require('./models/todo');
module.exports = function(app) {
// api ---------------------------------------------
// get all todos
app.get('/api/todos', function (req, res) {
Todo.find(function (err, todos) {
if (err)
res.send(err)
res.json(todos);
});
});
// create todo and send back all todos after creation
app.post('/api/todos', function (req, res) {
Todo.create({
text: req.body.text,
done: false
}, function (err, todo) {
if (err)
res.send(err);
Todo.find(function (err, todos) {
if (err)
res.send(err)
res.json(todos);
});
});
});
// delete a todo
app.delete('/api/todos/:todo_id', function (req, res) {
Todo.remove({
_id: req.params.todo_id
}, function (err, todo) {
if (err)
res.send(err);
Todo.find(function (err, todos) {
if (err)
res.send(err)
res.json(todos);
})
})
})
// application --------------------------------------
app.get('*', function (req, res) {
res.sendFile(__dirname + '/public/index.html');
});
};
You want to use the done callback but none of your tests declare it in the parameters of the callbacks passed to it. Your first test, for instance, should be:
it("returns status code 200", function (done) { // <== Add parameter here!
request(base_url, function(error, response, body) {
expect(response.statusCode).to.equal(200);
done();
});
});
Without the parameter, Mocha considers the test to be synchronous. So it does not wait for request to call its callback, and ends right away. The fact that done is undefined does not lead to an error because the JavaScript interpreter does not get to done() before Mocha deems the tests over.
I'm a JavaScript novice and had to change my code from
it('getReports', () => {
getReports()
.then((res) => {
assert.equal(200, res.statusCode);
});
});
to
it('getReports', () => getReports()
.then((res) => {
assert.equal(200, res.statusCode);
}));
i.e. Had to remove the first set of curly brackets.
After this the Mocha tests reported an error.
Starting with Node 8 you can use the native async/await approach for requests and testing.
First use request-promise or request-promise-native instead request.
const request = require('request-promise-native');
Tests with async/await:
// testing success results - any error will fail the test
it('Returns status code 200', async () => {
const response = await request(base_url);
expect(response.statusCode).to.equal(200);
});
// testing for a particular error
it('Testing a particular error is thrown', async () => {
let error;
try {
await request(base_url);
} catch (err) {
error = err;
}
expect(error).to.be.ok;
expect(error.message).to.equal('Expected error message');
});
In my case running test files with the below command solved the problem.
node --unhandled-rejections=strict node_modules/.bin/mocha --require #babel/register --require babel-polyfill test/**/*.test.js

Node.js with Express - throw Error vs next(error)

Can someone expound on the times when it's appropriate in a node.js Express app to throw an error like so:
throw new Error('my error');
or to pass this error on via the callback usually labelled 'next' like so:
next(error);
and could you please explain what each of them will do in the context of an Express app?
for example, here is an express function dealing with URL parameters:
app.param('lineup_id', function (req, res, next, lineup_id) {
// typically we might sanity check that user_id is of the right format
if (lineup_id == null) {
console.log('null lineup_id');
req.lineup = null;
return next(new Error("lineup_id is null"));
}
var user_id = app.getMainUser()._id;
var Lineup = app.mongooseModels.LineupModel.getNewLineup(app.system_db(), user_id);
Lineup.findById(lineup_id, function (err, lineup) {
if (err) {
return next(err);
}
if (!lineup) {
console.log('no lineup matched');
return next(new Error("no lineup matched"));
}
req.lineup = lineup;
return next();
});
});
In the line commented "//should I create my own error here?"
I could used "throw new Error('xyz')", but what exactly would that do? Why is it usually better to pass the error to the callback 'next'?
Another question is - how do I get "throw new Error('xyz')" to show up in the console as well as the browser when I am in development?
In general express follows the way of passing errors rather than throwing it, for any errors in the program you can pass the error object to 'next', also an error handler needs to be defined so that all the errors passed to 'next' can be handled properly.
http://expressjs.com/en/guide/error-handling.html
Throwing an error inside a callback doesn't work:
app.get('/', function (req, res) {
fs.mkdir('.', (err) => {
if (err) throw err;
});
});
But calling next works:
app.get('/', function (req, res, next) {
fs.mkdir('.', (err) => {
if (err) next(err);
});
});
Errors that occur in synchronous code inside route handlers and middleware require no extra work. If synchronous code throws an error, then Express will catch and process it. For example:
app.get('/', function (req, res) {
throw new Error('BROKEN') // Express will catch this on its own.
})
For those who prefer throwing errors, here is a workaround decorator:
export function safeThrow(
target: object,
key: string | symbol,
descriptor: TypedPropertyDescriptor<(req: Request, res: Response, next: NextFunction) => Promise<any>>) {
const fun = descriptor.value;
descriptor.value = async function () {
try {
await fun.apply(this, arguments);
} catch (err) {
arguments[2](err);
}
};
}
#safeThrow
private async get(req: Request, res: Response, next: NextFunction) {
throw { status: 404, message: 'Not supported' }
}

Resources