Promise resolve 后如何正确测试 setTimeout

How properly test setTimeout after Promise resolve

如何正确测试此功能。

  1. 设置超时
  2. 超时回调
  3. 和递归调用
export function initScheduler(timeout: number, callback: () => Promise<void>): void {
  setTimeout(() => {
    callback().then(() => {
      initScheduler(timeout, callback);
    });
  }, timeout);
}

我试过

describe('initScheduler', () => {
  it('should call on schedule', () => {
    jest.useFakeTimers();

    const timeout: number = 60000;
    const callback: jest.Mock = jest.fn().mockResolvedValue(undefined);

    initScheduler(timeout, callback);

    expect(setTimeout).toHaveBeenCalledTimes(1);
    expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), timeout);
    expect(callback).not.toBeCalled();

    jest.advanceTimersByTime(timeout);

    expect(callback).toBeCalled();
    expect(setTimeout).toHaveBeenCalledTimes(2);
  });
});

但最后的期望returns1

不要专注于针对 setTimeout 的断言。专注于您的功能预期执行的操作。

describe('', () => {
  jest.useFakeTimers();
  const timeout = 300;
  const callback = jest.fn();

  beforeEach(() => {
    jest.clearAllTimers();
    callback
      .mockClear()
      .mockReturnValue(Promise.resolve()); 
  });

  it('runs callback only after delay given', () => {
    initScheduler(timeout, callback);
    jest.advanceTimersByTime(timeout - 1);
    expect(callback).not.toHaveBeenCalled();
    jest.advanceTimersByTime(2);
    expect(callback).toHaveBeenCalledTimes(1);
  });

  it('reruns scheduler if callback been resolved successfully', async () => {
    initScheduler(timeout, callback);
    expect(callback).not.toHaveBeenCalled();
    jest.advanceTimersByTime(timeout);
    await Promise.resolve();
    jest.advanceTimersByTime(timeout);
    await Promise.resolve();
    jest.advanceTimersByTime(timeout);
    await Promise.resolve();
    expect(callback).toHaveBeenCalledTimes(3);
  });

  it('stops scheduler if callback rejected', async () => {
    callback.mockReturnValue(Promise.reject());
    initScheduler(timeout, callback);
    jest.advanceTimersByTime(timeout);
    await Promise.resolve();
    jest.advanceTimersByTime(timeout);
    await Promise.resolve();
    expect(callback).toHaveBeenCalledTimes(1);
  });
});

一些细节。

  1. 我很惊讶,但在没有 jest.clearAllTimers() 的情况下,计时器在 it() 之间没有被清除。可能取决于 jsdom 实现或 jest 版本,我不知道。
  2. 没有 await Promise.resolve() 你的模拟 callback 没有 运行 .then 部分。实际上它可能是 await <anything else>,我只是看到 await Promise.resolve(); 看起来没有 await 42; 那么神奇。不管怎样,它的目的是直接刷新 microtasks queue while jest itself does not provide API 就可以了。