Javascript Jest模拟计时器不能按预期异步工作;如何使这次考试通过?

Javascript Jest模拟计时器不能按预期异步工作;如何使这次考试通过?,javascript,typescript,unit-testing,timer,jestjs,Javascript,Typescript,Unit Testing,Timer,Jestjs,我正在尝试测试一个队列组件,它可以进行调用并处理大量调度。我想用一个模拟api来测试它,api响应会像在现实生活中一样延迟,但我想用模拟计时器来模拟时间的流逝。在下面的简单示例中,被测试的对象是调用者对象 function mockCall(): Promise<string> { return new Promise<string>(resolve => setTimeout(() => resolve("success"), 20)); } co

我正在尝试测试一个队列组件,它可以进行调用并处理大量调度。我想用一个模拟api来测试它,api响应会像在现实生活中一样延迟,但我想用模拟计时器来模拟时间的流逝。在下面的简单示例中,被测试的对象是调用者对象

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();
});
函数mockCall():Promise{
返回新承诺(resolve=>setTimeout(()=>resolve(“success”),20);
}
const callReceiver=jest.fn((结果:string)=>{console.log(结果)});
类调用者{
构造函数(调用:()=>Promise,
接收方:(结果:字符串)=>void){
调用(),然后调用(接收方);
}
}
它(“正确推进模拟计时器”,()=>{
开玩笑。使用faketimers();
新呼叫者(mockCall、callReceiver);
开玩笑提前计时(50);
expect(callReceiver).toHaveBeenCalled();
});
我认为这个测试应该通过,但是在定时器提前之前,会对
expect
进行评估,因此测试失败。如何编写此测试以使其通过


顺便说一句,如果我使用实时计时器并将
expect
延迟20毫秒以上,这个测试确实通过了,但我特别感兴趣的是使用假计时器并用代码推进时间,而不是等待实时过去

您可以通过将承诺返回给jest,使测试工作正常,否则您的测试方法的执行已经完成,并且不会等待承诺实现

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

您可以通过将承诺返回给jest来实现测试,否则测试方法的执行已经完成,并且不会等待承诺的实现

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

原因是
mockCall
仍然返回承诺,即使在模拟计时器之后。因此,
call()。然后()
将作为下一个微任务执行。要推进执行,您也可以将
expect
包装到microtask中:

it("advances mock timers correctly", () => {
  jest.useFakeTimers();
  new Caller(mockCall, callReceiver);
  jest.advanceTimersByTime(50);
  return Promise.resolve().then(() => {
    expect(callReceiver).toHaveBeenCalled()
  });
});
当心不要收回这个承诺,这样玩笑会一直等到它完成。对我来说,使用async/await会更好:

it("advances mock timers correctly", async () => {
   jest.useFakeTimers();
   new Caller(mockCall, callReceiver);
   jest.advanceTimersByTime(50);
   await Promise.resolve();
   expect(callReceiver).toHaveBeenCalled();
});
顺便说一句,每次模拟返回的
Promise
(例如
fetch
)时都会发生同样的事情-您将需要像使用假计时器一样推进微任务队列

有关微任务/宏任务队列的详细信息:


Jest repo已公开提议以更明确的方式处理未决承诺,但迄今为止还没有ETA。

原因是
mockCall
仍然返回承诺,即使在您模拟计时器之后。因此,
call()。然后()
将作为下一个微任务执行。要推进执行,您也可以将
expect
包装到microtask中:

it("advances mock timers correctly", () => {
  jest.useFakeTimers();
  new Caller(mockCall, callReceiver);
  jest.advanceTimersByTime(50);
  return Promise.resolve().then(() => {
    expect(callReceiver).toHaveBeenCalled()
  });
});
当心不要收回这个承诺,这样玩笑会一直等到它完成。对我来说,使用async/await会更好:

it("advances mock timers correctly", async () => {
   jest.useFakeTimers();
   new Caller(mockCall, callReceiver);
   jest.advanceTimersByTime(50);
   await Promise.resolve();
   expect(callReceiver).toHaveBeenCalled();
});
顺便说一句,每次模拟返回的
Promise
(例如
fetch
)时都会发生同样的事情-您将需要像使用假计时器一样推进微任务队列

有关微任务/宏任务队列的详细信息:

Jest repo已公开提议以更明确的方式处理未决承诺,但目前还没有ETA