Getting a UnhandledPromiseRejectionWarning when testing using mocha/chai
Solution 1
The issue is caused by this:
.catch((error) => {
assert.isNotOk(error,'Promise error');
done();
});
If the assertion fails, it will throw an error. This error will cause done()
never to get called, because the code errored out before it. That's what causes the timeout.
The "Unhandled promise rejection" is also caused by the failed assertion, because if an error is thrown in a catch()
handler, and there isn't a subsequent catch()
handler, the error will get swallowed (as explained in this article). The UnhandledPromiseRejectionWarning
warning is alerting you to this fact.
In general, if you want to test promise-based code in Mocha, you should rely on the fact that Mocha itself can handle promises already. You shouldn't use done()
, but instead, return a promise from your test. Mocha will then catch any errors itself.
Like this:
it('should transition with the correct event', () => {
...
return new Promise((resolve, reject) => {
...
}).then((state) => {
assert(state.action === 'DONE', 'should change state');
})
.catch((error) => {
assert.isNotOk(error,'Promise error');
});
});
Solution 2
For those who are looking for the error/warning UnhandledPromiseRejectionWarning
outside of a testing environment, It could be probably because nobody in the code is taking care of the eventual error in a promise:
For instance, this code will show the warning reported in this question:
new Promise((resolve, reject) => {
return reject('Error reason!');
});
(node:XXXX) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Error reason!
and adding the .catch()
or handling the error should solve the warning/error
new Promise((resolve, reject) => {
return reject('Error reason!');
}).catch(() => { /* do whatever you want here */ });
Or using the second parameter in the then
function
new Promise((resolve, reject) => {
return reject('Error reason!');
}).then(null, () => { /* do whatever you want here */ });
Solution 3
The assertion libraries in Mocha work by throwing an error if the assertion was not correct. Throwing an error results in a rejected promise, even when thrown in the executor function provided to the catch
method.
.catch((error) => {
assert.isNotOk(error,'Promise error');
done();
});
In the above code the error
objected evaluates to true
so the assertion library throws an error... which is never caught. As a result of the error the done
method is never called. Mocha's done
callback accepts these errors, so you can simply end all promise chains in Mocha with .then(done,done)
. This ensures that the done method is always called and the error would be reported the same way as when Mocha catches the assertion's error in synchronous code.
it('should transition with the correct event', (done) => {
const cFSM = new CharacterFSM({}, emitter, transitions);
let timeout = null;
let resolved = false;
new Promise((resolve, reject) => {
emitter.once('action', resolve);
emitter.emit('done', {});
timeout = setTimeout(() => {
if (!resolved) {
reject('Timedout!');
}
clearTimeout(timeout);
}, 100);
}).then(((state) => {
resolved = true;
assert(state.action === 'DONE', 'should change state');
})).then(done,done);
});
I give credit to this article for the idea of using .then(done,done) when testing promises in Mocha.
Solution 4
I got this error when stubbing with sinon.
The fix is to use npm package sinon-as-promised when resolving or rejecting promises with stubs.
Instead of ...
sinon.stub(Database, 'connect').returns(Promise.reject( Error('oops') ))
Use ...
require('sinon-as-promised');
sinon.stub(Database, 'connect').rejects(Error('oops'));
There is also a resolves method (note the s on the end).
See http://clarkdave.net/2016/09/node-v6-6-and-asynchronously-handled-promise-rejections
Solution 5
I faced this issue:
(node:1131004) UnhandledPromiseRejectionWarning: Unhandled promise rejection (re jection id: 1): TypeError: res.json is not a function (node:1131004) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.j s process with a non-zero exit code.
It was my mistake, I was replacing res
object in then(function(res)
, so changed res
to result and now it is working.
Wrong
module.exports.update = function(req, res){
return Services.User.update(req.body)
.then(function(res){//issue was here, res overwrite
return res.json(res);
}, function(error){
return res.json({error:error.message});
}).catch(function () {
console.log("Promise Rejected");
});
Correction
module.exports.update = function(req, res){
return Services.User.update(req.body)
.then(function(result){//res replaced with result
return res.json(result);
}, function(error){
return res.json({error:error.message});
}).catch(function () {
console.log("Promise Rejected");
});
Service code:
function update(data){
var id = new require('mongodb').ObjectID(data._id);
userData = {
name:data.name,
email:data.email,
phone: data.phone
};
return collection.findAndModify(
{_id:id}, // query
[['_id','asc']], // sort order
{$set: userData}, // replacement
{ "new": true }
).then(function(doc) {
if(!doc)
throw new Error('Record not updated.');
return doc.value;
});
}
module.exports = {
update:update
}
Related videos on Youtube
Jzop
Updated on March 03, 2021Comments
-
Jzop about 3 years
So, I'm testing a component that relies on an event-emitter. To do so I came up with a solution using Promises with Mocha+Chai:
it('should transition with the correct event', (done) => { const cFSM = new CharacterFSM({}, emitter, transitions); let timeout = null; let resolved = false; new Promise((resolve, reject) => { emitter.once('action', resolve); emitter.emit('done', {}); timeout = setTimeout(() => { if (!resolved) { reject('Timedout!'); } clearTimeout(timeout); }, 100); }).then((state) => { resolved = true; assert(state.action === 'DONE', 'should change state'); done(); }).catch((error) => { assert.isNotOk(error,'Promise error'); done(); }); });
On the console I'm getting an 'UnhandledPromiseRejectionWarning' even though the reject function is getting called since it instantly shows the message 'AssertionError: Promise error'
(node:25754) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): AssertionError: Promise error: expected { Object (message, showDiff, ...) } to be falsy
- should transition with the correct event
And then, after 2 sec I get
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
Which is even weirder since the catch callback was executed(I think that for some reason the assert failure prevented the rest of the execution)
Now the funny thing, if I comment out the
assert.isNotOk(error...)
the test runs fine without any warning in the console. It stills 'fails' in the sense that it executes the catch.
But still, I can't understand these errors with promise. Can someone enlighten me?-
Redu over 7 yearsI think you have one extra set of closing brace and parens at the very last line. Please delete them and try again.
-
Benjamin Gruenbaum over 7 yearsThis is so cool, the new unhandled rejection warning finds bugs in real life and saves people time. So much win here. Without this warning your tests would have timed out without any explanation.
-
Nick Radford about 7 yearsFor anyone curious, this is also true for jasmine.
-
TheCrazyProgrammer almost 7 years@robertklep Won't catch will get called for any error and not just the one you're expecting? I think this style won't work if you're trying to assert failures.
-
robertklep almost 7 years@TheCrazyProgrammer the
catch
handler should probably be passed as second argument tothen
. However, I'm not entirely sure what the intention of the OP was, so I left it as-is. -
Paweł over 6 yearsAnd also for anyone curious about jasmine, use
done.fail('msg')
in this case. -
Louis over 6 yearsYou do not have to call it as
await helperFunction(...)
. Anasync
function returns a promise. You could just handle the returned promise like you'd do on a function not markedasync
that happens to return a promise. The point is to handle the promise, period. Whether the function isasync
or not does not matter.await
is merely one among multiple ways to handle the promise. -
Frank Nocke over 6 yearsTrue. But then I have to invest lines on catching... or my tests pass as false positives, and any failing promises will go unoticed (in terms of testrunner results). So await looks like less lines&effort to me. – Anyway, forgetting await was, what caused that
UnhandledPromiseRejectionWarning
for me... thus this answer. -
Andrew almost 6 yearsSinon now includes the methods "resolves" and "rejects" for stubs as of version 2. See npmjs.com/package/sinon-as-promised. I still +1'ed the answer, though - I didn't know about this.
-
bjm88 over 5 yearscan you give full example of where main code vs assertions should go, not so clear here.....especially if you asserting a value from the original service/promise call in our code. Does the actual promise for our code go within this other "mocha promise" ??
-
robertklep over 5 years@bjm88 perhaps it helps if consider your test case as a
.then
handler: you return a promise chain from it, and typically, that chain starts with the function under test, followed by a.then
where you will assert that the value(s) returned are correct. See this gist. -
mikep over 5 yearsOf course but I think in real-life we usually do not use just
new Promise((resolve, reject) => { return reject('Error reason!'); })
but in functionfunction test() { return new Promise((resolve, reject) => { return reject('Error reason!'); });}
so inside function we do not need to use.catch()
but to successfully handle errors it is enough to use when calling that functiontest().catch(e => console.log(e))
or async/await versiontry { await test() } catch (e) { console.log(e) }
-
Riley Steele Parsons over 4 yearsTo use the default behavior provided by mocha, simply return the promise with no
catch
. It will mark the test as failed if the promise is rejected and pretty print the error. -
Sandoval0992 over 2 yearsI fixed the same error this way, by adding a missing catch block during database connection.