Getting a UnhandledPromiseRejectionWarning when testing using mocha/chai

499,277

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
}
Share:
499,277

Related videos on Youtube

Jzop
Author by

Jzop

Updated on March 03, 2021

Comments

  • Jzop
    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

    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?

    • Redu
      Redu over 7 years
      I think you have one extra set of closing brace and parens at the very last line. Please delete them and try again.
    • Benjamin Gruenbaum
      Benjamin Gruenbaum over 7 years
      This 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
    Nick Radford about 7 years
    For anyone curious, this is also true for jasmine.
  • TheCrazyProgrammer
    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
    robertklep almost 7 years
    @TheCrazyProgrammer the catch handler should probably be passed as second argument to then. However, I'm not entirely sure what the intention of the OP was, so I left it as-is.
  • Paweł
    Paweł over 6 years
    And also for anyone curious about jasmine, use done.fail('msg') in this case.
  • Louis
    Louis over 6 years
    You do not have to call it as await helperFunction(...). An async function returns a promise. You could just handle the returned promise like you'd do on a function not marked async that happens to return a promise. The point is to handle the promise, period. Whether the function is async or not does not matter. await is merely one among multiple ways to handle the promise.
  • Frank Nocke
    Frank Nocke over 6 years
    True. 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
    Andrew almost 6 years
    Sinon 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
    bjm88 over 5 years
    can 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
    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
    mikep over 5 years
    Of course but I think in real-life we usually do not use just new Promise((resolve, reject) => { return reject('Error reason!'); }) but in function function 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 function test().catch(e => console.log(e)) or async/await version try { await test() } catch (e) { console.log(e) }
  • Riley Steele Parsons
    Riley Steele Parsons over 4 years
    To 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
    Sandoval0992 over 2 years
    I fixed the same error this way, by adding a missing catch block during database connection.