【问题标题】:Jest unit test - How do I call through async function in a setTimeout repeating function开玩笑单元测试 - 我如何通过 setTimeout 重复函数中的异步函数调用
【发布时间】:2019-08-13 14:02:40
【问题描述】:

更新:可以肯定地说这基本上是Jest: Timer and Promise don't work well. (setTimeout and async function)的副本


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

我观察到我们可以调用以重复该功能,但在要求 jest 提前计时器时它不会“通过”。当您删除它之前的异步函数时,它确实有效。所以听起来我必须让 doAsyncStuff 在这里“调用”或解决一些未决的承诺?

功能

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

测试

jest.useFakeTimers();

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

repeatMe();

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

jest.advanceTimersByTime(5000);
expect(doAsyncStuff).toHaveBeenCalledTimes(2); // Failed?

【问题讨论】:

    标签: javascript reactjs jestjs


    【解决方案1】:

    所以听起来我必须...在这里解决一些未决的承诺?

    是的,没错。


    简短的回答是,then 链接到 doAsyncStuff 返回的 Promise,Promise 回调在 PromiseJobs 中排队,并且测试的编写方式,该回调永远不会有机会一直跑到测试结束。

    要修复它,请给 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 ...
    });
    

    关于发生的事情和原因的完整细节可以在my answer here中找到

    【讨论】:

      【解决方案2】:

      显然,Jest 故障排除引用了此问题:we have set the definition to happen asynchronously on the next tick of the event loop。我想这也适用于实时代码中的事件,而不仅仅是书面测试。

      执行jest.runAllTimers() 将使事情陷入无限循环。

      添加jest.runAllTicks() 将提前勾选,因此上述测试现在可以工作。

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

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

      https://jestjs.io/docs/en/troubleshooting#defining-tests


      这个工作还需要模拟 doAsyncStuff 的实现,因为我认为 doAsyncStuff 中的任何异步内容也会排队到滴答事件中。

                  doAsyncStuff.mockImplementation(() => {
                      return new Promise.resolve();
                  });
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-11-09
        • 2021-09-13
        • 1970-01-01
        • 1970-01-01
        • 2020-03-11
        相关资源
        最近更新 更多