Javascript 以承诺取笑假计时器

Javascript 以承诺取笑假计时器,javascript,promise,jestjs,Javascript,Promise,Jestjs,在使用假计时器和承诺的组合时,让Jest测试框架(23.2.0版)正常工作有点困难。我哪里做错了 假设我有以下模块: // timing.js export const timeout = ms => new Promise(resolve => { setTimeout(resolve, ms) }) 我的测试文件如下所示: // timing.test.js import { timeout } from './timing' describe('time

在使用假计时器和承诺的组合时,让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)
。。。考试会通过的

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)
  })
})
目前,它不受支持 你没有做错任何事-现在不起作用-对不起。在我们开始工作之前,必须发生以下事情:

  • Jest需要合并正在进行的工作,将lolex合并为其伪计时器实现
  • Lolex需要支持兑现承诺——我们在最近的Node.js collaborator峰会上与V8团队讨论了这一点。这将公开一个钩子,我们将使用它来执行类似于
    advanceTimeByTime(100)
    的操作,并让它与承诺一起工作
gist中的问题是
.then(spy)
只会在以后调用

由于我们是志愿者,这些事情没有具体的时间表。我希望SimenB在未来2-3个月内完成合并,下个月我将与V8团队一起跟进hook

你现在能做什么 您始终可以编写异步测试:

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

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

在我的例子中,计时器回调调用了其他异步函数,因此其他解决方案对我不起作用。我最终发现,通过手动确保承诺队列为空,所有异步代码都将完成运行,我可以让测试正常工作:

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(...);
});
因为您可以在两种不同的伪计时器实现之间进行选择

我发现,
jest.useFakeTimers('legacy')
与使用的承诺一起工作,但它不与日期一起工作,而
jest.useFakeTimers('modern')
与日期一起工作,但不与承诺一起工作,因为
等待flushPromises()
从未解决

我发现最好的解决方案是改用它,因为它既适用于承诺,也适用于日期,没有任何解决方法或黑客:

从“@sinonjs/fake timers”导入FakeTimers;
//测试前:
const clock=FakeTimers.install();
//在测试中:
等待时钟。异步(100);
//测试后:
clock.uninstall();

您不能执行以下操作:const promise=createTimerPromise()jest.advanceTimerByTime(100)wait promise您可以,这将是一个额外的行,我希望能够直接等待函数结果。有人知道它是否仍然是真的,Jest不支持它吗?@jesper它是真的,但仍然不支持它-你需要使用变通方法。是的,这对我测试承诺有很大帮助。非常感谢。
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(...);
});