使用 mocha/chai 进行测试时获得 UnhandledPromiseRejectionWarning

IT技术 javascript node.js promise mocha.js chai
2021-02-03 21:58:13

所以,我正在测试一个依赖于事件发射器的组件。为此,我想出了一个使用 Promises 和 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();
  });
});

在控制台上,即使调用了拒绝函数,我也会收到“UnhandledPromiseRejectionWarning”,因为它会立即显示消息“AssertionError: Promise error”

(节点:25754)UnhandledPromiseRejectionWarning:未处理的Promise拒绝(拒绝ID:2):AssertionError:Promise错误:预期{Object(message,showDiff,...)}为假

  1. 应该转换为正确的事件

然后,2秒后我得到

错误:超过 2000 毫秒的超时时间。确保在此测试中调用 done() 回调。

由于执行了 catch 回调,这甚至更奇怪(我认为由于某种原因,断言失败阻止了其余的执行)

现在有趣的是,如果我注释掉assert.isNotOk(error...)测试运行良好,控制台中没有任何警告。从它执行捕获的意义上说,它仍然“失败”。
但是,我仍然无法通过Promise来理解这些错误。有人可以启发我吗?

6个回答

问题是由以下原因引起的:

.catch((error) => {
  assert.isNotOk(error,'Promise error');
  done();
});

如果断言失败,它将抛出错误。这个错误会导致done()never 被调用,因为代码在它之前出错了。这就是超时的原因。

“未处理的Promise拒绝”也由断言失败造成的,因为如果一个错误在被抛出catch()的处理程序,并没有后续的catch()处理程序,错误将得到吞噬(说明这篇文章)。UnhandledPromiseRejectionWarning警告提醒您注意这一事实。

一般来说,如果你想在 Mocha 中测试基于 promise 的代码,你应该依赖 Mocha 本身已经可以处理 promise 的事实。你不应该使用done(),而是从你的测试中返回一个 promise。然后摩卡会自己捕捉任何错误。

像这样:

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');
  });
});
@robertklep 不会因为任何错误而被调用,而不仅仅是您期望的错误?我认为如果你试图断言失败,这种风格是行不通的。
2021-03-21 21:58:13
@TheCrazyProgrammercatch处理程序可能应该作为第二个参数传递给then. 但是,我不完全确定 OP 的意图是什么,所以我保持原样。
2021-03-25 21:58:13
对于任何好奇的人来说,茉莉花也是如此。
2021-04-05 21:58:13
你能给出主代码和断言应该去哪里的完整例子,这里不太清楚......特别是如果你在我们的代码中从原始服务/Promise调用中断言一个值。我们代码的实际Promise是否在另一个“摩卡Promise”中?
2021-04-08 21:58:13
对于任何对茉莉花感兴趣的人,请done.fail('msg')在这种情况下使用
2021-04-09 21:58:13

对于那些UnhandledPromiseRejectionWarning在测试环境之外寻找错误/警告的人来说,这可能是因为代码中没有人处理Promise中的最终错误:

例如,此代码将显示此问题中报告的警告:

new Promise((resolve, reject) => {
  return reject('Error reason!');
});

(node:XXXX) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Error reason!

并添加.catch()或处理错误应该解决警告/错误

new Promise((resolve, reject) => {
  return reject('Error reason!');
}).catch(() => { /* do whatever you want here */ });

或者使用then函数中的第二个参数

new Promise((resolve, reject) => {
  return reject('Error reason!');
}).then(null, () => { /* do whatever you want here */ });
当然,但我认为在现实生活中我们通常不只是new Promise((resolve, reject) => { return reject('Error reason!'); })在函数中使用function test() { return new Promise((resolve, reject) => { return reject('Error reason!'); });}所以在函数内部我们不需要使用,.catch()但为了成功处理错误,在调用该函数test().catch(e => console.log(e))或异步/等待版本时使用它就足够了try { await test() } catch (e) { console.log(e) }
2021-04-13 21:58:13
我通过在数据库连接期间添加丢失的 catch 块以这种方式修复了相同的错误。
2021-04-13 21:58:13

我在用 sinon 存根时遇到了这个错误。

解决方法是在使用存根解决或拒绝Promise时使用 npm 包sinon-as-promised

代替 ...

sinon.stub(Database, 'connect').returns(Promise.reject( Error('oops') ))

利用 ...

require('sinon-as-promised');
sinon.stub(Database, 'connect').rejects(Error('oops'));

还有一个 resolves 方法(注意末尾的 s)。

http://clarkdave.net/2016/09/node-v6-6-and-asynchronously-handled-promise-rejections

从版本 2 开始,Sinon 现在包括存根的“解决”和“拒绝”方法。请参阅npmjs.com/package/sinon-as-promised不过,我仍然对答案 +1 了 - 我不知道这一点。
2021-03-23 21:58:13

如果断言不正确,Mocha 中的断言库通过抛出错误来工作。抛出错误会导致被拒绝的Promise,即使在提供给catch方法的执行程序函数中抛出也是如此

.catch((error) => {
  assert.isNotOk(error,'Promise error');
  done();
});

在上面的代码中,对象的error计算结果为,true所以断言库会抛出一个错误......它永远不会被捕获。由于错误,该done方法从未被调用。Mocha 的done回调接受这些错误,所以你可以简单地用.then(done,done). 这确保了 done 方法总是被调用,并且错误会以与 Mocha 在同步代码中捕获断言错误时相同的方式报告。

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

我将这篇文章归功于在 Mocha 中测试 promise 时使用 .then(done,done) 的想法。

我遇到了这个问题:

(node:1131004) UnhandledPromiseRejectionWarning: 未处理的Promise拒绝 (rejection id: 1): TypeError: res.json is not a function (node:1131004) DeprecationWarning: 不推荐使用未处理的Promise拒绝。将来,未处理的Promise拒绝将以非零退出代码终止 Node.js 进程。

这是我的错误,我正在替换 中的res对象then(function(res),因此更改res为 result 并且现在可以正常工作了。

错误的

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

更正

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

服务代码:

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
}