Getting a UnhandledPromiseRejectionWarning when testing using mocha/chai - javascript

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?

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');
});
});

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 */ });

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

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.

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
}

Here's my take experience with E7 async/await:
In case you have an async helperFunction() called from your test... (one explicilty with the ES7 async keyword, I mean)
→ make sure, you call that as await helperFunction(whateverParams) (well, yeah, naturally, once you know...)
And for that to work (to avoid ‘await is a reserved word’), your test-function must have an outer async marker:
it('my test', async () => { ...

I had a similar experience with Chai-Webdriver for Selenium.
I added await to the assertion and it fixed the issue:
Example using Cucumberjs:
Then(/I see heading with the text of Tasks/, async function() {
await chai.expect('h1').dom.to.contain.text('Tasks');
});

Just a heads-up that you can get a UnhandledPromiseRejectionWarning if you accidentally put your test code outside of the it-function. 😬
describe('My Test', () => {
context('My Context', () => {
it('should test something', () => {})
const result = testSomething()
assert.isOk(result)
})
})

Related

Should I avoid try catch in every single async/await on Node js?

This is a design question that came up to me while unit testing.
Let's dive into the example:
Imagine this:
async function foo() {
try {
return apiCall()
}
catch (e) {
throw new CustomError(e);
}
}
async function bar() {
return foo()
}
async function main() {
try {
await bar()
}catch(e) {
console.error(e)
}
}
main()
What do we see here? That the only function that hasn't got a try-catch block is bar.
But if foo fails, it should get catched by the main catch.
While unittesting this like
describe('testing bar', () => {
it('foo should throw', () => {
foo.mockImplementantion(() => { throw new CustomError('error')});
bar()
.then((result) => console.log(result))
.catch((err) => { exepect(err).toBeInstanceOf(CustomError)}) // this is what we are testing
})
})
The output we see is that an Unhandled promise rejection is logged in the console.
So, my question is... even if I know that the main() will catch the error, should I use try-catch block inside all async functions?
try..catch may be necessary if a function is able to recover from an error, do a side effect like logging, or re-throw a more meaningful error.
If CustomError is more preferable than an error that apiCall can throw then try..catch necessary, otherwise it doesn't. Also the problem with foo is that it handles only synchronous errors. In order to handle rejected promises, it should be return await apiCall(), this is a known pitfall of async.
Uncaught rejections are unwanted, they currently result in UnhandledPromiseRejectionWarning and are expected to crash Node in future versions. It's preferable to handle an error in a meaningful way at top level, so main needs to catch the error. This can be delegated to process uncaughtRejection event handler but it may be beneficial for it to stay extra level of error handling that should be never reached.
The output we see is that an Unhandled promise rejection is logged in the console.
This shouldn't happen. A rejection needs to be handled by the test. One possible point of failure is explained above, foo can return original error from apiCall instead of CustomError in case it wasn't correctly mocked, this will fail the expectation and result in unhandled rejection in catch(). Another point of failure is that the test has unchained promise because it wasn't returned, the test always passes.
Asynchronous test that uses promises should always return a promise. This can be improved by using async..await. foo is async, it's expected to always return a promise:
it('foo should throw', async () => {
foo.mockImplementantion(() => { return Promise.reject(new CustomError('error')) });
await expect(bar()).rejects.toThrow(CustomError);
})
Now even if foo mock fails (foo mock won't affect bar if they are defined in the same module as shown) and bar rejects with something that is not CustomError, this will be asserted.
No. You don't need to use try/catch in every async/await. You only need to do it at the top level. In this case your main function which you are already doing.
Weather you should is a matter of opinion. The go language designers feel strongly enough about this that is has become the standard in go to always handle errors at each function call. But this is not the norm in javascript or most other languages.
Unhandled promise rejection
Your unhandled promise rejection is thrown by your it() function because you are not telling it to wait for the promise to complete.
I assume you are using something like mocha for the unit test (other frameworks may work differently). In mocha there are two ways to handle asynchronous tests:
Call the done callback - the it() function will always be called with a done callback. It is up to you weather you want to use it or like in your posted code to not use it:
describe('testing bar', () => {
it('foo should throw', (done) => {
foo.mockImplementantion(() => { throw new CustomError('error')});
bar()
.then((result) => {
console.log(result);
done(); // ------------- THIS IS YOUR ACTUAL BUG
})
.catch((err) => {
exepect(err).toBeInstanceOf(CustomError);
done(); // ------------- THIS IS YOUR ACTUAL BUG
})
})
})
Return a Promise. If you return a promise to the it() function mocha will be aware that your code is asynchronous and wait for completion:
describe('testing bar', () => {
it('foo should throw', (done) => {
foo.mockImplementantion(() => { throw new CustomError('error')});
return bar() // <----------- THIS WOULD ALSO FIX IT
.then((result) => {
console.log(result);
})
.catch((err) => {
exepect(err).toBeInstanceOf(CustomError);
})
})
})
In short, there is nothing wrong with your code. But you have a bug in your unit test.
As #Bergi told me I will post some solutions right here
I wrap the function in a try catch block
1.
async function bar() {
try{
return foo()
} catch (e) {
throw e
}
}
Rewrite the test
describe('testing bar', () => {
it('foo should throw', (done) => {
foo.mockImplementantion(() => { throw new CustomError('error')});
bar()
.then((result) => { throw result }) // this is because we are expecting an error, so if the promise resolves it's actually a bad sign.
.catch((err) => {
exepect(err).toBeInstanceOf(CustomError)}) // this is what we are testing
done();
})
})
Use return in the test case
describe('testing bar', () => {
it('foo should throw', () => {
foo.mockImplementantion(() => { throw new CustomError('error')});
return bar()
.then((result) => { throw result })
.catch((err) => { exepect(err).toBeInstanceOf(CustomError)}) // this is what we are testing
})
})

Javascript Promises: testing error scenario

How do I test a promise that rejects with an error? (that is the expected behaviour)
I can not figure out why this test is failing. I'm catching the error rejected, and the test still fails. From the example provided at https://www.sitepoint.com/promises-in-javascript-unit-tests-the-definitive-guide/ this should work, and can't figure out how it's supposed to really. Both console logs appear.
it('shows that even an error occurs the test passes', (done) => {
const promise = new Promise((resolve, reject) => {
console.log('error promise is called');
setTimeout(() => {
reject(new Error('dummy error thrown'));
}, 100);
})
promise.then(() => {});
promise.catch((e) => {
console.log('error caught');
done();
});
});
1) promise behaviour in tests (async) - base test cases shows that even an error occurs the test passes:
Uncaught Error: dummy error thrown
at Timeout.setTimeout [as _onTimeout] (test\promise-spec.js:24:17)
Tried like this, still fail
promise
.then(() => {})
.catch((e) => {
console.log('error caught');
done();
});
Turns out it was because of a previous test that didn't call done, and it throwing an error killed this one

Handle exceptions within a returned promise

I know this is not the most beautiful code, but due to legacy issues, I need to stick to this workflow.
The problem is that I am not able to bubble up any exceptions that might occur in the heart of the returned promise.
The code is designed that both the reject and resolve return valid data. So, if you change the const CONDITION to 0.4, we will be getting a rejection. If the value of const CONDITION stays at 0.6, we will be getting a resolution. This works so far.
However, in cases where we have structural failures, such as in the example below where we try to pass a wrong variable name into the rejection:
let reasoxxxx = '__FAILED__';
reject({error: reason, data: output});
I am not able to invoke a throw to bubble up the error. For that reason, I am getting the usual ugly message and I cannot bubble up a proper exception:
Exchange request was rejected due to error(s).
(node:224) UnhandledPromiseRejectionWarning: ReferenceError: reason is not defined
(node:224) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:224) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Any ideas? The code snipped should work.
function fakeFetch() {
// Promisify the request.
return new Promise((resolve, reject) => {
// Emulate an asynchroneous fetch.
setTimeout(() => {
let result = 0.4; // Change to 0.4 to trigger a failed fetch.
if (result < 0.5) {;
reject('__FAIL__');
} else {
resolve({name: 'apple', price: '1234.12', time: 1549926859970});
}
}, 2000);
});
}
async function sendExchangeRequest(id, pair, symbols, callback)
{
let err, result
await fakeFetch().then((output) => { result = output }).catch((error) => {err = error})
if(err){
result = 'None'
}else{
err = 'None'
}
callback(err, result)
}
async function fetchExchangeData(id, pair, symbols) {
// Promisify the request.
try {
return new Promise((resolve, reject) => {
try {
// Send the request.
sendExchangeRequest(id, pair, symbols, ((err, output) => {
try{
if(err){
// Soft Failure
console.log('Exchange request was rejected due to error(s).');
reject({error: err, data: output});
}else{
// Success
console.log('Exchange request was successful.');
resolve({error: err, data: output});
}
} catch(error) {
throw error;
}
}));
} catch(error) {
console.log('---\n', error, '\n---');
throw error;
}
});
} catch(error) {
// Bubble up the error?
console.log('+++\n', error, '\n+++');
throw error;
}
}
(async () => {
await fetchExchangeData('myid', 'MYPAIR', 'mySymbol')
.then((result) => console.log(result))
.catch((failure) => console.log(failure))
})();
--- EDIT (01) ---
I have updated my example snipped to include a fake API call. I hope this makes my question a bit more clear.
I think you're not understanding the concept of async functions. Your first try/catch in fetchExchangeData do essentially nothing, and there is nothing async in it. The try/catch inside the initial promise, is also almost useless, as I'm sure most/all issues with sendExchangeRequest will likely be dealt with the error handler function (err). As for the try/catch blocks, you shouldn't throw errors inside the promise, you should instead reject the error (Which will bubble it up). Only try/catch if you're in an async function, and NOT using callbacks.
function fetchExchangeData(id, pair, symbols) {
// Promisify the request.
return new Promise((resolve, reject) => {
try {
// Send the request.
sendExchangeRequest(id, pair, symbols, ((err, output) => {
try{
if(err){
// Soft Failure
console.log('Exchange request was rejected due to error(s).');
let reason = '__FAILED__';
reject({error: reason, data: output});
}else{
// Success
console.log('Exchange request was successful.');
resolve({error: '__NONE__', data: output});
}
} catch(error) {
reject(error);
}
}));
} catch(error) {
console.log('---\n', error, '\n---');
reject(error);
}
});
}

Optional catch in javascript promises

The following is valid:
new Promise<void>((resolve, reject) => {
reject()
})
.then(() => {})
.catch(() => {})
But I might not always care about the error. Is there a way to make the catch optional?
I tried this but it didn't work:
new Promise<void>((resolve, reject?) => {
if (reject) reject()
})
.then(() => {})
Error: Uncaught (in promise): undefined
Is there a way to make the catch optional?
No. If you are using a promise that might error, you need to handle that (or propagate it to your caller).
Of course if you create the promise yourself, rejecting it is optional, and you can choose to never reject your promises so that you won't need to handle any errors. But if there are errors from promises that you are using, and you want to ignore them, you must do so explicitly. Just write
somePromise.catch(e => void e);
// or () => { /* ignore */ }
// or function ignore() {}
I was trying to solve the same issue, and finally come up with the following promise wrapper:
/**
* wraps a given promise in a new promise with a default onRejected function,
* that handles the promise rejection if not other onRejected handler is provided.
*
* #param customPromise Promise to wrap
* #param defaultOnRejected Default onRejected function
* #returns wrapped promise
*/
export function promiseWithDefaultOnRejected(customPromise: Promise<any>, defaultOnRejected: (_: any) => any): Promise<any> {
let hasCatch = false;
function chain(promise: Promise<any>) {
const newPromise: Promise<any> = new Promise((res, rej) => {
return promise.then(
res,
function(value) {
if (hasCatch) {
rej(value);
} else {
defaultOnRejected(value);
}
},
);
});
const originalThen = newPromise.then;
// Using `defineProperty` to not overwrite `Promise.prototype.then`
Object.defineProperty(newPromise, 'then', {
value: function (onfulfilled: any, onrejected: any) {
const result: Promise<any> = originalThen.call(newPromise, onfulfilled, onrejected);
if (typeof onrejected === 'function') {
hasCatch = true;
return result;
} else {
return chain(result);
}
}
});
return newPromise;
}
return chain(customPromise);
}
This function lets you wrap your promises with a defaultOnRejected function that will handle the rejected promise if no other handler is provided. For example:
const dontCare = promiseWithDefaultOnRejected(Promise.reject("ignored"), () => {});
The result promise will never throw an "Unhandled Promise Rejection", and you can use it as follows:
dontCare.then(x=>console.log("never happens")).catch(x=>console.log("happens"));
or
dontCare.then(x=>console.log("never happens"), x=>console.log("happens"));
or simply without onRejected handler:
dontCare.then(x=>console.log("never happens")).then(x=>console.log("also never happens"));
An issue with this util is that it is not working as expected with async/await syntax: you need to propagate and handle the "catch" path as follows:
async () => {
try {
await promiseWithDefaultOnRejected(Promise.reject("ignored"), () => {})
.catch((e) => { throw e; });
} catch (e) {
console.log("happens");
}
}
You could resolve when the error is something you don't care about. If your catch returns anything other than a rejected promise, the error isn't propagated down the chain.
const ignorableError = new Error("I don't care about this error");
const myPromise = new Promise((resolve, reject) => {
reject(ignorableError);
})
.then(() => {})
.catch(error => {
if(error == ignorableError) {
console.log("Error ignored");
return;
}
// Do something else...
});
myPromise.then(() => console.log("Success"))
Let me try to describe your situation:
You have a service that gets user information and a function called getUser that uses that service. When the service fails for any reason then getUser does not have a user available. The result of getUser is used quite a lot of times in your code with the following situation(s):
User is available run a function (block of code).
User is not available run a function (block of code).
Run a function with the error/reject of the service.
When using getUser result you may want to run all 3 functions, a combination of 2 of them or only one.
Current getUser returns a Promise and this type does not seem to be suitable for your situation. Mainly because rejecting a promise and not catching it will cause unhandled promise rejection. And because if you want to run code if user is available or not it complicates the functions (they both have to check the result instead of assuming user is or is not available).
Maybe you can try the following code, please be careful making assumptions in the not available block, this could be due to any error. For example: it does not mean the user does not exist because it could be a network error.
In the example getUser is used but can be any function that returns a promise where you assume not available on reject.
const isAvailable = promise => {
//do not expose NOT_AVAILABLE outside this function
const NOT_AVAILABLE = {type:"not available"};
//no uncaught promise rejection errors by VM
const savePromise = promise.catch(x=>x);
return {
available:fn=>
promise
.catch(e=>Promise.reject(NOT_AVAILABLE))
.then(fn)
.catch(
e=>
(e===NOT_AVAILABLE)
? undefined//ignore
: Promise.reject(e)//re throw, error is not from service
),
//call not available with the promise if promise rejects
notAvailable:fn=>promise.catch(()=>fn(promise)),
catchError:promise.catch.bind(promise)
};
}
const service = arg =>
(arg===1)
? Promise.resolve(arg)
: Promise.reject(arg)
const getUser = arg => isAvailable(service(arg));
var user = getUser(2);
//if service failed available will be skipped
user.available(
user=>console.log("skipped:",user)
);
//both catch and notAvailable will be called
user.notAvailable(
arg=>console.log("not available:",arg)
);
user.notAvailable(
arg=>console.log("still not available:",arg)
);
//not handling catchError does not cause uncaught promise exception
// but you can inspect the error
// user.catchError(
// err=>console.log("error is::",err)
// );
var user = getUser(1);
//you can call available on user multiple times
user.available(
user=>console.log("got user:",user)
);
user.available(
user=>Promise.resolve(user)
.then(user=>console.log("still got user:",user))
.then(x=>Promise.reject("have to catch this one though"))
.catch(e=>console.log("ok, got error trying to run available block:",e))
);
//showing you can inspect the error
var user = getUser(5);
user.catchError(err=>console.log("error from service:",err));

Making assertions with catch in promises exceeds timeout

I want to make an assertion in a catch block of a promise chain, but it reaches the timeout. Assertions work in then blocks, but it seems in the catch block, done() is never reached. Is it being suppressed? Is there a better way to test promise rejections?
import assert from 'assert';
import { apicall } from '../lib/remoteapi';
describe('API calls', function () {
it('should test remote api calls', function (done) {
apicall([])
.then((data) => {
assert.equal(data.items.length, 2); // this works fine
done();
})
.catch((e) => {
console.log('e', e);
assert.equal(e, 'empty array'); // ?
done(); // not reached?
});
});
});
The promise rejection
apicall(channelIds) {
if(channelIds.length === 0) return Promise.reject('empty array');
...
}
I get this error:
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
If this is mocha and you're using Promises, don't use the callback functionality; it makes things very awkward as you're discovering. Instead, mocha lets you return the promise in your test. A rejected Promise means a failed test, and a successful Promise means a succeeding test. A failed assertion causes the code to throw an exception, which will automatically fail the Promise, which is usually what you'll want. In short:
describe('API calls', function () {
it('should test remote api calls', function () {
return apicall([])
.then((data) => {
assert.equal(data.items.length, 2);
});
});
});

Categories