【问题标题】:Testing asynchronous code in Jest: done() not being called as expected在 Jest 中测试异步代码:done() 未按预期调用
【发布时间】:2019-03-07 07:42:53
【问题描述】:

这是一个用 Jest (v20.0.4) 编写的测试套件。

前 3 个测试是预期行为,我的问题与 Test4 有关。

test('Test1: the list should contain 7', () => {
  const data = [1, 2, 7, 9];
  expect(data).toContain(7);
});
// > Passes as expected
test('Test2: the list should contain 7', () => {
  const data = [1, 2, 7, 9];
  expect(data).toContain(8);
});
// > Fails as expected; Expected array: [1, 2, 7, 9] To contain value: 8
test('Test3: the list should contain 7', (done) => {
  function callback(data) {
    expect(data).toContain(7);
    done();
  }
  setTimeout(() => {
    callback([1, 2, 7, 9]);
  }, 500);
});
// > Passes as expected
test('Test4: the list should contain 7', (done) => {
  function callback(data) {
    expect(data).toContain(8);
    done();
  }
  setTimeout(() => {
    callback([1, 2, 7, 9]);
  }, 500);
});
// > Fails with Error "Timeout - Async callback was not invoked within timeout specified"

这是我的问题:

在 Test4 中,done()expect 语句之后立即被调用。
所以,即使期望语句没有通过,我猜它应该会失败并出现类似于 Test2 的错误:(Expected array: [1, 2, 7, 9] To contain value: 8)

但是,它失败并出现如上所示的超时错误,这表明从未调用过 done()

为什么?没看懂!

有人可以指导我完成这种行为吗?

我扫描了docs,但找不到与此相关的任何内容。

【问题讨论】:

  • 我相信 Jest 一旦看到一个失败的期望语句就会返回。但是关于如何在期望失败后立即调用 done() 的任何想法,而不是等待超时发生。

标签: javascript unit-testing jestjs


【解决方案1】:

在您的代码中,expect 调用会抛出,done 永远不会到达。因此,Jest 会一直等到超时并发出您注意到的令人讨厌的、不清楚的错误。

解决方案是捕获 throw 并调用 done(error) 向 Jest runner 指示测试失败。

test('Test4: the list should contain 7', done => {
  function callback(data) {
    try {
      expect(data).toContain(8);
      done();
    }
    catch (err) {
      done(err);
    }
  }
  setTimeout(() => {
    callback([1, 2, 7, 9]);
  }, 500);
});

这样运行会得到想要的结果:

Error: expect(received).toContain(expected) // indexOf

Expected value: 8
Received array: [1, 2, 7, 9]

来自docs

如果expect 语句失败,它会抛出一个错误并且done() 不会被调用。如果我们想在测试日志中查看失败的原因,我们必须将expect 包装在try 块中,并将catch 块中的错误传递给done。否则,我们最终会出现不透明的超时错误,该错误不会显示expect(data) 收到的值。

【讨论】:

    【解决方案2】:

    我发现setTimeout 和 Jest 并不像人们想象的那样工作。但是有一个很好的方法来处理它:

    • 在测试开始时,您可以告诉 Jest 使用 fakeTimers: jest.useFakeTimers();

    • 当您需要执行回调时,您可以使用jest.runAllTimers();触发它

    https://jestjs.io/docs/en/timer-mocks.html

    【讨论】:

    • 感谢您的回答。计时器模拟绝对是一个不错的选择。但是,我认为使用“承诺模式”比使用 done() 的“回调模式”更好
    • 两者都不是“更好”——它们有两种不同的用途。是的,如果你有使用承诺的代码,承诺模式会更清晰,但有时你想测试不提供承诺的仅回调函数,在这种情况下,done 是唯一的选择(没有手动承诺回调)。在 OP 的代码中,测试在达到 done 之前抛出,但解决方案是捕获错误并使用它调用 donedone(error) 向 Jest 发出测试失败的信号。这样可以避免讨厌的超时。
    猜你喜欢
    • 2018-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-11
    • 1970-01-01
    相关资源
    最近更新 更多