开玩笑地假诺言

时间:2018-07-01 20:29:17

标签: javascript promise jestjs

在结合使用伪造的计时器和Promise时,让Jest测试框架(版本23.2.0)正常工作时遇到了一些麻烦。我要去哪里错了?

假设我有以下模块:

// timing.js

export const timeout = ms =>
  new Promise(resolve => {
    setTimeout(resolve, ms)
  })

我的测试文件如下:

// timing.test.js

import { timeout } from './timing'

describe('timeout()', () => {
  beforeEach(() => {
    jest.useFakeTimers()
  })

  it('resolves in a given amount of time', () => {
    const spy = jest.fn()

    timeout(100).then(spy)
    expect(spy).not.toHaveBeenCalled()

    jest.advanceTimersByTime(100)
    expect(spy).toHaveBeenCalled()
  })
})

这将失败,并显示以下输出:

● timeout › resolves in a given amount of time

expect(jest.fn()).toHaveBeenCalled()

Expected mock function to have been called, but it was not called.

  15 |
  16 |     jest.advanceTimersByTime(100)
> 17 |     expect(spy).toHaveBeenCalled()
     |                 ^
  18 |   })
  19 | })
  20 |

  at Object.<anonymous> (src/timing.test.js:17:17)

但是,如果我删除了承诺:

// timing.js
export const timeout = ms => ({
  then: resolve => {
    setTimeout(resolve, ms)
  }
})

...测试将通过

timeout
  ✓ resolves in a given amount of time (5ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.304s

更新

尽管这不是最优雅的解决方案,但我目前正在使用以下测试。它有效,但是我仍然对为什么原始版本不起作用感到好奇

import { timeout } from './timing'

describe('timeout', () => {
  it('resolves in a given amount of time', done => {
    setTimeout(() => done(new Error('it didn\'t resolve or took longer than expected')), 10)
    return timeout(9).then(done)
  })
})

3 个答案:

答案 0 :(得分:11)

目前不支持

您没有做错任何事情-目前无法正常工作-对不起。必须先进行以下操作,才能从我们的工作开始:

  • Jest需要合并正在进行的工作,以便在https://github.com/facebook/jest/pull/5171
  • 处将lolex合并为伪计时器实现。
  • Lolex需要支持兑现承诺-我们在最近的Node.js协作者峰会上与V8团队进行了讨论。那会暴露一个钩子,我们将使用它来做类似advanceTimeByTime(100)的事情,并使它与promises一起工作。

要点是,.then(spy)仅被称为以后

由于我们是志愿者-这些事情没有具体的时间表。我希望SimenB在未来2-3个月内进行合并,我将在下个月继续与V8团队联系。

您现在可以做什么

您始终可以编写异步测试:

// note this is an async function now
it('resolves in a given amount of time', async () => {
  // this is in a promise.reoslve.then to not 'lock' on the await
  Promise.resolve().then(() => jest.advanceTimersByTime(100));
  await timeout(100);
});

如果您还有其他需要等待的时间,可以在超时后添加期望值。

答案 1 :(得分:5)

jest@26.0.0起,您可以在两个不同的伪计时器实现之间进行选择。

我发现jest.useFakeTimers('legacy')适用于使用the flushPromises workaround的Promises,但不适用于Date,而jest.useFakeTimers('modern')适用于Date但不适用于await flushPromises()Date起就无法解决。

我发现最好的解决方案是改用@sinonjs/fake-timers,因为该解决方案可与Promises和import FakeTimers from "@sinonjs/fake-timers"; // Before tests: const clock = FakeTimers.install(); // In tests: await clock.tickAsync(100); // After tests: clock.uninstall(); 一起使用,而没有任何变通方法或hack:

users

答案 2 :(得分:1)

在我的情况下,计时器回调调用了其他异步函数,因此其他解决方案对我不起作用。我最终得出结论,通过手动确保promise队列为空,所有异步代码都将运行完毕,并且我可以使测试正常进行:

function flushPromises() {
  // Wait for promises running in the non-async timer callback to complete.
  // From https://stackoverflow.com/a/58716087/308237
  return new Promise(resolve => setImmediate(resolve));
}

test('example', async () => {
  jest.useFakeTimers();

  example_function_to_set_a_timer();

  // Wait for one virtual second
  jest.advanceTimersByTime(1000);

  // Wait for any async functions to finish running
  await flushPromises();

  // Continue with tests as normal
  expect(...);
});