简体   繁体   English

开玩笑的模拟计时器无法按预期方式异步工作; 如何使该测试通过?

[英]Jest mock timers not working as expected asynchronously; how to make this test pass?

I am trying to test a queuing component that makes calls and handles a lot of scheduling. 我正在尝试测试一个进行呼叫并处理大量计划的排队组件。 I want to test it with a mock api where the api responses are delayed as they would be in real life, but I want to use mock timers and fake the passage of time. 我想用一个模拟api进行测试,该API的api响应会像在现实生活中那样被延迟,但是我想使用模拟计时器并伪造时间的流逝。 In the following bare-bones example, the object under test is the Caller object. 在下面的简单示例中,被测试的对象是Caller对象。

function mockCall(): Promise<string> {
    return new Promise<string>(resolve => setTimeout(() => resolve("success"), 20));
}

const callReceiver = jest.fn((result: string) => { console.log(result)});

class Caller {
    constructor(call: () => Promise<string>,
                receiver: (result: string) => void) {
        call().then(receiver);
    }
}

it("advances mock timers correctly", () => {
   jest.useFakeTimers();
   new Caller(mockCall, callReceiver);
   jest.advanceTimersByTime(50);
   expect(callReceiver).toHaveBeenCalled();
});

I would think this test should pass, but instead the expect is evaluated before the timer is advanced, so the test fails. 我认为该测试应该通过,但是在计时器提前之前评估了expect ,因此该测试失败。 How can I write this test so it will pass? 如何编写此测试以便通过?

By the way, this test does pass if I use real timers and delay the expect for more than 20 milliseconds, but I am specifically interested in using fake timers and advancing time with code, not waiting for real time to elapse. 顺便说一句,如果我使用真正的计时器并将expect延迟超过20毫秒,则此测试确实通过了,但是我特别希望使用伪造的计时器并使用代码增加时间,而不是等待实时时间过去。

You can make the test work by returning the promise to jest as otherwise the execution of your test method is already finished and does not wait for the promise to be fulfilled. 您可以通过将诺言返回给开玩笑来使测试工作,否则测试方法的执行已经完成并且不等待诺言被兑现。

function mockCall() {
  return new Promise(resolve => setTimeout(() => resolve('success'), 20));
}

const callReceiver = jest.fn((result) => { console.log(result); });

class Caller {

  constructor(callee, receiver) {
    this.callee = callee;
    this.receiver = receiver;
  }

  execute() {
    return this.callee().then(this.receiver);
  }
}

describe('my test suite', () => {
  it('advances mock timers correctly', () => {
    jest.useFakeTimers();
    const caller = new Caller(mockCall, callReceiver);
    const promise = caller.execute();
    jest.advanceTimersByTime(50);

    return promise.then(() => {
      expect(callReceiver).toHaveBeenCalled();
    });
  });
});

The reason is mockCall still returns Promise, even after you mocked timer. 原因是即使在mockCall计时器之后, mockCall仍会返回Promise。 So call().then() will be executed as next microtask. 因此call().then()将作为下一个微任务执行。 To advance execution you can wrap your expect in microtask too: 要提高执行速度,您也可以将expect包装在微任务中:

it("advances mock timers correctly", () => {
  jest.useFakeTimers();
  new Caller(mockCall, callReceiver);
  jest.advanceTimersByTime(50);
  return Promise.resolve().then(() => {
    expect(callReceiver).toHaveBeenCalled()
  });
});

Beware of returning this Promise so jest would wait until it's done. 谨防返回此Promise,所以开玩笑会等到完成为止。 To me using async/await it would look even better: 对我来说,使用异步/等待看起来会更好:

it("advances mock timers correctly", async () => {
   jest.useFakeTimers();
   new Caller(mockCall, callReceiver);
   jest.advanceTimersByTime(50);
   await Promise.resolve();
   expect(callReceiver).toHaveBeenCalled();
});

Btw the same thing each time you mock something that is returning Promise (eg fetch ) - you will need to advance microtasks queue as well as you do with fake timers. 每次模拟返回Promise东西(例如fetch )时,都会发生同样的事情-您将需要像使用假计时器一样推进微任务队列。

More on microtasks/macrotasks queue: https://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f 有关微任务/宏任务队列的更多信息: https ://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f

Jest repo has open proposal on handling pending Promises in more clear way https://github.com/facebook/jest/issues/2157 but no ETA so far. Jest repo已公开提议以更清晰的方式处理待处理的Promises https://github.com/facebook/jest/issues/2157,但到目前为止没有ETA。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM