如何在 Jest 中正确使用 Promises 和 Timers

How to properly use Promises and Timers with Jest

我搜索了 SO 和 Google,发现了很多类似的问题和答案,但其中 none 似乎帮助我解决了我的问题。

我正在尝试编写一些需要模拟异步轮询功能的测试用例。但无论我做什么,我都会得到:

未在 jest.setTimeout.Timeout

指定的 5000 毫秒超时内调用异步回调

我设置了一些重现问题的最小测试用例:

jest.useFakeTimers();

describe('timers test', () => {
  it('plain timer works as expected', () => {
    const mock = jest.fn();
    setTimeout(mock, 5000);

    jest.runAllTimers();
    expect(mock).toHaveBeenCalled();
  });

  it('Using a timer to mock a promise resolution results in a jest timeout error', async () => {
    const mock = jest.fn(() => {
      return new Promise((resolve) => setTimeout(resolve, 500));
    });

    const handler = jest.fn();

    await mock().then(handler);

    jest.runAllTimers();

    expect(handler).toHaveBeenCalled();
  });

  it('Using a timer to mock a promise rejection results in a jest timeout error', async () => {
    const mock = jest.fn(() => {
      return new Promise((resolve, reject) => setTimeout(reject, 500));
    });

    const handler = jest.fn();

    await mock().catch(handler);

    jest.runAllTimers();

    expect(handler).toHaveBeenCalled();
  });
});

有人可以解释我做错了什么以及为什么吗?

因此,在@Bergi 的后续评论中,我认为 done 实际上也不是必需的。我只是需要重新订购一些东西。然后我 运行 在测试承诺链时遇到了类似的问题,进一步强调了这一点,所以我为此添加了一些案例。

jest.useFakeTimers();

describe('timers test', () => {
  it('Using a plain timer works as expected', () => {
    const mock = jest.fn();
    setTimeout(mock, 5000);

    jest.runAllTimers();
    expect(mock).toHaveBeenCalled();
  });

  it('Using a timer to mock a promise resolution', async () => {
    const mock = jest.fn(() => {
      return new Promise((resolve) => setTimeout(resolve, 500));
    });

    const handler = jest.fn();

    const actual = mock().then(handler);
    jest.runAllTimers();
    await actual;

    expect(handler).toHaveBeenCalled();
  });

  it('Using a timer to mock a promise rejection', async () => {
    const mock = jest.fn(() => {
      return new Promise((resolve, reject) => setTimeout(reject, 500));
    });

    const handler = jest.fn();

    const actual = mock().catch(handler);
    jest.runAllTimers();
    await actual;

    expect(handler).toHaveBeenCalled();
  });

  it('Using a timer to mock a promise resolve -> delay -> resolve chain', async () => {
    const mockA = jest.fn(() => {
      return Promise.resolve();
    });

    const mockB = jest.fn(() => {
      return new Promise((resolve, reject) => {
        setTimeout(resolve, 500);
      });
    });

    const handler = jest.fn();

    const actual = mockA()
      .then(() => {
        const mockProm = mockB();
        jest.runAllTimers();
        return mockProm;
      })
      .then(handler);

    jest.runAllTimers();
    await actual;

    expect(mockA).toHaveBeenCalled();
    expect(mockB).toHaveBeenCalled();
    expect(handler).toHaveBeenCalled();
  });

  it('Using a timer to mock a promise resolve -> delay -> reject chain', async () => {
    const mockA = jest.fn(() => {
      return Promise.resolve();
    });

    const mockB = jest.fn(() => {
      return new Promise((resolve, reject) => {
        setTimeout(reject, 500);
      });
    });

    const handler = jest.fn();

    const actual = mockA()
      .then(() => {
        const mockProm = mockB();
        jest.runAllTimers();
        return mockProm;
      })
      .catch(handler);


    await actual;

    expect(mockA).toHaveBeenCalled();
    expect(mockB).toHaveBeenCalled();
    expect(handler).toHaveBeenCalled();
  });
});

@Bergi 的评论让我找到了解决方案。我最终使用了 done 函数,并删除了 await。这似乎至少在这个最小的测试用例中有效。

jest.useFakeTimers();

describe('timers test', () => {
  it('plain timer works as expected', () => {
    const mock = jest.fn();
    setTimeout(mock, 5000);

    jest.runAllTimers();
    expect(mock).toHaveBeenCalled();
  });

  it('Using a timer to mock a promise resolution results in a jest timeout error', async (done) => {
    const mock = jest.fn().mockImplementation(() => {
      return new Promise((resolve) => setTimeout(resolve, 500));
    });

    // make the handler invoke done to replace the await    
    const handler = jest.fn(done);

    mock().then(handler);
    jest.runAllTimers();

    expect(handler).toHaveBeenCalled();
  });

  it('Using a timer to mock a promise rejection results in a jest timeout error', async (done) => {
    const mock = jest.fn().mockImplementation(() => {
      return new Promise((resolve, reject) => setTimeout(reject, 500));
    });

    // make the handler invoke done to replace the await
    const handler = jest.fn(done);

    mock().catch(handler);
    jest.runAllTimers();

    expect(handler).toHaveBeenCalled();
  });
});