【问题标题】:Mocha - How to test for unsettled promise?Mocha - 如何测试未解决的承诺?
【发布时间】:2025-12-16 09:30:01
【问题描述】:

我正在测试一个返回承诺的函数。我想断言,在某些情况下,返回的承诺永远不会解决(不解决也不拒绝)。

如何使用 Mocha 进行测试?


如果我运行以下命令:

describe('under certain conditions', function () {
  let promise;
  beforeEach(function () {
    promise = new Promise((resolve, reject) => {});
  });
  it('should hang forever', function () {
    return promise;
  });
});

我收到以下错误:

Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves

【问题讨论】:

  • 定义“从不”。
  • 如上面给出的示例 - new Promise((resolve, reject) => {}) 永远不会解决,对吧?
  • 你真的不能。确保承诺永远不会被解决的唯一方法是证明没有仍然引用它的 resolve/reject 函数。所以像gc.collect(); const a = gc.memory(); promise = null; gc.collect(); const b = gc.memory(); return a - b < 0 这样的东西可能会做到,但它看起来并不可靠。
  • @dayuloli 您是否等待了无限长的时间才能确定?
  • @dayuloli 见*.com/a/30654451/1048572(我认为我的语法错误,我也怀疑它是否有效,但你可以试试)

标签: javascript testing promise mocha.js es6-promise


【解决方案1】:

robertklepanswer 有效,但您必须等待 5 秒才能完成测试。对于单元测试,5 秒实在是太长了。

正如您所建议的,您可以将 lolex 库集成到 robertklep 的解决方案中,以避免等待。

(我也使用Symbol而不是字符串'timeout',以防您的promise解决,巧合的是,也使用字符串'timeout'解决)

import { install } from 'lolex';

describe('A promise', function () {
  let clock;
  before(function () { clock = install() });
  after(function () { clock.uninstall() });

  describe('under certain conditions', function () {
    const resolvedIndicator = Symbol('resolvedIndicator');
    const forever = 600000; // Defining 'forever' as 10 minutes
    let promise;
    beforeEach(function () {
      promise = Promise.race([
        new Promise(() => {}), // Hanging promise
        new Promise(resolve => setTimeout(resolve, forever, resolvedIndicator)),
      ]);
    });
    it('should hang forever', function () {
      clock.tick(forever);
      return promise.then((val) => {
        if (val !== resolvedIndicator) {
          throw Error('Promise should not have resolved');
        }
      }, () => {
        throw Error('Promise should not have rejected');
      });
    });
  });
});

【讨论】:

  • +1 用于使用Symbol。至于使用lolex,那不是只有在 actual 用例依赖于某种超时时才有用吗?我认为 OP 并不是真的想测试 new Promise(() => {}) 永远不会解决,而是在他们的应用程序中做出一些承诺。此外,它可能会干扰 Mocha 自己的计时。
  • 当你运行lolex.install()时,它会覆盖setTimeout的全局实例,此时,它会存储当前时间及其内部纪元(从0开始)。当您运行clock.tick() 时,它只是将该时期转发该数量并检查是否需要执行任何回调。所以也许lolex确实会干扰Mocha的计时,因为它会覆盖全局setTimeout,但在这种情况下,我认为这并不重要,因为它不会超时,我们在测试后恢复原始行为. (您可以使用内部beforeEach / afterEach 以获得更多粒度)
  • (至少我认为lolex会这样工作,我还没有查看源代码)
【解决方案2】:

让我们首先说明,实际上,不可能验证承诺从不结算:在某些时候,您必须确定承诺结算的时间过长,并假设在那之后它永远不会安定下来。

这里有一个解决方案,可以将该点设置为 5 秒:

it('should hang forever', function() {
  // Disable Mocha timeout for this test.
  this.timeout(0);

  // Wait for either a timeout, or the promise-under-test to settle. If the
  // promise that settles first is not the timeout, fail the test.
  return Promise.race([
    new Promise(resolve => setTimeout(resolve, 5000, 'timeout')),
    promise.then(
      () => { throw Error('unexpectedly resolved') },
      () => { throw Error('unexpectedly rejected') }
    )
  ]);
});

【讨论】:

  • 您可能想做Promise.race([delay(…), promise.then(() => { throw … }, () => { throw … })]) 而不是事后处理分辨率并比较"timeout"
【解决方案3】:

试试这个:

describe('under certain conditions', function () {

    let promise;
    beforeEach(function () {
        promise = new Promise((resolve, reject) => {
        // promise.reject();
      });
    });

    it('should hang forever', function (done) {
        const onRejectOrResolve = () => {
            done(new Error('test was supposed to hang'));
        };
        promise
        .then(onRejectOrResolve)
        .catch(onRejectOrResolve);
        setTimeout(() => {
            done();
        }, 1000);
    });

  });

【讨论】:

  • 无论是任何工具/库(不仅仅是 Mocha)。您必须限制测试表示中的“永远”,即在标记测试用例通过/失败之前要等待多长时间。由于 mocha 的默认超时时间为 2000 毫秒,因此我将您的等待限制设置为 1000 毫秒。您可以随时调整 mocha 测试超时,但请确保您的等待时间应少于此时间。
【解决方案4】:

您可以使用Promise.race (MDN) 在永不解决的承诺和具有适当超时的参考承诺之间引入竞争:

const p1 = new Promise((resolve, reject) => { });

const p2 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 5 * 1000, 'promise2');
});


Promise.race([p1, p2])
.then(value => { 
  console.log(value); 
});

【讨论】: