【问题标题】:Getting a UnhandledPromiseRejectionWarning when testing using mocha/chai使用 mocha/chai 进行测试时收到 UnhandledPromiseRejectionWarning
【发布时间】:2017-02-04 14:35:10
【问题描述】:

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

(node:25754) UnhandledPromiseRejectionWarning: 未处理的承诺 拒绝(拒绝 id:2):AssertionError:承诺错误:预期 { Object (message, showDiff, ...) } 是假的

  1. 应该使用正确的事件进行转换

然后,2秒后我得到

错误:超过 2000 毫秒的超时。确保 done() 回调是 在这个测试中被调用。

自从执行了 catch 回调之后就更奇怪了(我认为由于某种原因,断言失败阻止了其余的执行)

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

【问题讨论】:

  • 我认为您在最后一行有一组额外的右括号和括号。请删除它们并重试。
  • 这太酷了,新的未处理拒绝警告可以发现现实生活中的错误并节省人们的时间。在这里赢了这么多。如果没有此警告,您的测试将在没有任何解释的情况下超时。

标签: javascript node.js promise mocha.js chai


【解决方案1】:

问题是由这个引起的:

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

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

“未处理的承诺拒绝”也是由失败的断言引起的,因为如果在catch()处理程序中抛出错误,并且没有后续的catch()处理程序,错误将被吞没(如this article 中所述)。 UnhandledPromiseRejectionWarning 警告提醒您注意这一事实。

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

像这样:

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 Won't catch 会因任何错误而被调用,而不仅仅是您期望的错误?如果你试图断言失败,我认为这种风格是行不通的。
  • @TheCrazyProgrammer catch 处理程序可能应该作为第二个参数传递给then。但是,我不完全确定 OP 的意图是什么,所以我保持原样。
  • 对于任何对茉莉花感兴趣的人,在这种情况下使用done.fail('msg')
  • 你能给出完整的例子来说明主代码和断言应该去哪里吗,这里不太清楚.....特别是如果你在我们的代码中断言来自原始服务/承诺调用的值。我们代码的实际承诺是否在另一个“摩卡承诺”中??
【解决方案2】:

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

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

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))或async/await版本try { await test() } catch (e) { console.log(e) }时足够使用了
  • 我用这种方式修复了同样的错误,在数据库连接期间添加了一个丢失的 catch 块。
【解决方案3】:

我在使用 sinon 存根时遇到此错误。

解决方法是在解决或拒绝带有存根的承诺时使用 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

【讨论】:

  • Sinon 现在包括版本 2 的存根的“resolves”和“rejects”方法。请参阅npmjs.com/package/sinon-as-promised。不过,我仍然为答案 +1 - 我不知道。
【解决方案4】:

如果断言不正确,Mocha 中的断言库会抛出错误。抛出错误会导致 promise 被拒绝,即使在提供给 catch 方法的执行器函数中抛出错误也是如此。

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

在上面的代码中,error objected 的计算结果为true,因此断言库会抛出一个错误......永远不会被捕获。由于错误,done 方法永远不会被调用。 Mocha 的done 回调接受这些错误,因此您可以简单地用.then(done,done) 结束Mocha 中的所有承诺链。这确保了 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);
});

我感谢 this article 在 Mocha 中测试 Promise 时使用 .then(done,done) 的想法。

【讨论】:

    【解决方案5】:

    我遇到了这个问题:

    (node:1131004) UnhandledPromiseRejectionWarning: 未处理的承诺拒绝(重新 射出 id: 1): TypeError: res.json is not a function (节点:1131004)DeprecationWarning:不推荐使用未处理的承诺拒绝。 将来,未处理的 Promise 拒绝将终止 Node.j s 具有非零退出代码的进程。

    这是我的错误,我正在替换 then(function(res) 中的 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
    }
    

    【讨论】:

      【解决方案6】:

      这是我对E7 async/await 的体验:

      如果您从测试中调用了 async helperFunction()...(我的意思是 ES7 async 关键字的一个显式)

      → 确保,您将其称为await helperFunction(whateverParams)(嗯,是的,当然,一旦您知道...)

      为了让它工作(避免“等待是一个保留字”),你的测试函数必须有一个外部异步标记:

      it('my test', async () => { ...
      

      【讨论】:

      • 你没有称它为await helperFunction(...)async 函数返回一个承诺。您可以像处理未标记为 async 的函数一样处理返回的承诺,该函数恰好返回一个承诺。关键是要处理承诺,期间。函数是否为async 无关紧要。 await 只是处理承诺的多种方式中的一种
      • 是的。但是随后我必须投入资金来捕捉......否则我的测试会作为误报通过,并且任何失败的承诺都将被忽视(就测试运行结果而言)。所以 await 对我来说似乎更少的线条和努力。 – 无论如何,忘记 await 是什么导致了 UnhandledPromiseRejectionWarning 对我来说......因此这个答案。
      【解决方案7】:

      我在使用 Chai-Webdriver for Selenium 时也有类似的经历。 我在断言中添加了await,它解决了这个问题:

      使用 Cucumberjs 的示例:

      Then(/I see heading with the text of Tasks/, async function() {
          await chai.expect('h1').dom.to.contain.text('Tasks');
      });
      

      【讨论】:

        【解决方案8】:

        请注意,如果您不小心将测试代码放在了 it 函数之外,您可以获得 UnhandledPromiseRejectionWarning。 ?

            describe('My Test', () => {
              context('My Context', () => {
                it('should test something', () => {})
                const result = testSomething()
                assert.isOk(result)
                })
              })
        

        【讨论】:

          猜你喜欢
          • 2019-02-15
          • 2017-02-18
          • 2023-03-22
          • 1970-01-01
          • 2018-01-02
          • 2015-10-01
          • 1970-01-01
          • 2014-11-25
          • 2016-05-26
          相关资源
          最近更新 更多