Javascript Jest单元测试-如何在setTimeout重复函数中通过异步函数调用

Javascript Jest单元测试-如何在setTimeout重复函数中通过异步函数调用,javascript,reactjs,jestjs,Javascript,Reactjs,Jestjs,更新:可以肯定地说,这基本上是 我有一个函数,它在执行异步函数后会自动重复。我想验证jest.advanceTimersOnTime(5000)是否继续生成expect(doAsyncStuff).toHaveBeenCalledTimes(X) 我观察到,我们可以通过调用来重复该函数,但当要求jest提前计时时,它不会“通过”。当您删除它前面的异步函数时,它确实起作用。所以听起来我必须让doAsyncStuff“通过电话”或解决一些悬而未决的承诺 作用 试验 显然,Jest疑难解答引用了这个

更新:可以肯定地说,这基本上是


我有一个函数,它在执行异步函数后会自动重复。我想验证
jest.advanceTimersOnTime(5000)
是否继续生成
expect(doAsyncStuff).toHaveBeenCalledTimes(X)

我观察到,我们可以通过调用来重复该函数,但当要求jest提前计时时,它不会“通过”。当您删除它前面的异步函数时,它确实起作用。所以听起来我必须让doAsyncStuff“通过电话”或解决一些悬而未决的承诺

作用 试验
显然,Jest疑难解答引用了这个问题:
我们已将定义设置为在事件循环的下一个勾号时异步发生。我想这也适用于实时代码中的事件,而不仅仅是书面测试

执行
jest.runAllTimers()
将使事情陷入无休止的循环

添加
jest.runAllTicks()
将提高滴答声,因此上述测试现在可以工作了

我仍然很困惑,但我想这就是答案

jest.advanceTimersByTime(5000);
jest.runAllTicks();
expect(doAsyncStuff).toHaveBeenCalledTimes(1);

jest.advanceTimersByTime(5000);
jest.runAllTicks();
expect(doAsyncStuff).toHaveBeenCalledTimes(2); 


这项工作还需要模拟doAsyncStuff的实现,因为我认为doAsyncStuff中的任何异步内容都会排队进入tick事件

            doAsyncStuff.mockImplementation(() => {
                return new Promise.resolve();
            });
听起来我必须…解决一些悬而未决的承诺

是的,没错


简单的回答是,
Promise
回调被
排队,然后
链接到
doAsyncStuff
返回的
Promise
,按照编写测试的方式,在测试结束之前,回调永远不会有机会运行

要解决此问题,请在测试期间给
Promise
回调一次运行的机会:

updater.js

export const doAsyncStuff = async () => { };
code.js

import { doAsyncStuff } from './updater';

export function repeatMe() {
  setTimeout(() => {
    doAsyncStuff().then((response) => {
      if (response) {
        console.log("I get here!");
        repeatMe();
      }
    })
  }, 5000);
}
code.test.js

import * as updater from './updater';
import { repeatMe } from './code';

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

  let doAsyncStuff = jest.spyOn(updater, 'doAsyncStuff');
  doAsyncStuff.mockResolvedValue(true);

  repeatMe();

  jest.advanceTimersByTime(5000);
  expect(doAsyncStuff).toHaveBeenCalledTimes(1);  // Success!

  await Promise.resolve();  // let callbacks in PromiseJobs run

  jest.advanceTimersByTime(5000);
  expect(doAsyncStuff).toHaveBeenCalledTimes(2);  // Success!

  await Promise.resolve();  // let callbacks in PromiseJobs run

  jest.advanceTimersByTime(5000);
  expect(doAsyncStuff).toHaveBeenCalledTimes(3);  // Success!

  // ... and so on ...
});

发生了什么以及为什么会发生的完整细节可以在

import { doAsyncStuff } from './updater';

export function repeatMe() {
  setTimeout(() => {
    doAsyncStuff().then((response) => {
      if (response) {
        console.log("I get here!");
        repeatMe();
      }
    })
  }, 5000);
}
import * as updater from './updater';
import { repeatMe } from './code';

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

  let doAsyncStuff = jest.spyOn(updater, 'doAsyncStuff');
  doAsyncStuff.mockResolvedValue(true);

  repeatMe();

  jest.advanceTimersByTime(5000);
  expect(doAsyncStuff).toHaveBeenCalledTimes(1);  // Success!

  await Promise.resolve();  // let callbacks in PromiseJobs run

  jest.advanceTimersByTime(5000);
  expect(doAsyncStuff).toHaveBeenCalledTimes(2);  // Success!

  await Promise.resolve();  // let callbacks in PromiseJobs run

  jest.advanceTimersByTime(5000);
  expect(doAsyncStuff).toHaveBeenCalledTimes(3);  // Success!

  // ... and so on ...
});