Getting a UnhandledPromiseRejectionWarning when testing using mocha/chai

Javascriptnode.jsPromisemocha.jsChai

Javascript Problem Overview


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 > 1) 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?

Javascript Solutions


Solution 1 - Javascript

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 - Javascript

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 - Javascript

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 4 - Javascript

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 5 - Javascript

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
}

Solution 6 - Javascript

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 () => { ...

Solution 7 - Javascript

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

Solution 8 - Javascript

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJzopView Question on Stackoverflow
Solution 1 - JavascriptrobertklepView Answer on Stackoverflow
Solution 2 - JavascriptgsalgadotoledoView Answer on Stackoverflow
Solution 3 - Javascriptdanday74View Answer on Stackoverflow
Solution 4 - JavascriptMatthew OrlandoView Answer on Stackoverflow
Solution 5 - JavascriptMuhammad ShahzadView Answer on Stackoverflow
Solution 6 - JavascriptFrank NockeView Answer on Stackoverflow
Solution 7 - JavascriptRichardView Answer on Stackoverflow
Solution 8 - JavascriptCiryonView Answer on Stackoverflow