【问题标题】:useFakeTimers and async callbackuseFakeTimers 和异步回调
【发布时间】:2019-01-18 14:39:35
【问题描述】:

考虑这个函数

function startTimerWithAsyncCallback(
    firstAsyncFunction,
    secondAsyncFunction,
    thirdAsyncFunction,
    millis,
) {
    setTimeout(async () => {
        await firstAsyncFunction();
        await secondAsyncFunction();
        await thirdAsyncFunction();
    }, millis);
}

我想测试 3 个异步函数是否在超时后被调用,使用开玩笑的假计时器。

test('fake timers', () => {
    jest.useFakeTimers();

    const firstAsyncFunction = jest.fn();
    const secondAsyncFunction = jest.fn();
    const thirdAsyncFunction = jest.fn();

    startTimerWithAsyncCallback(
        firstAsyncFunction,
        secondAsyncFunction,
        thirdAsyncFunction,
        1000,
    );
    jest.advanceTimersByTime(2000);

    expect(firstAsyncFunction).toHaveBeenCalled();
    expect(secondAsyncFunction).toHaveBeenCalled();  // FAILS HERE !
    expect(thirdAsyncFunction).toHaveBeenCalled();
});

通过此测试,第一个异步函数处于挂起状态,并且不会调用下一个异步函数。在进行断言之前,我没有找到一种方法来告诉:“等待 setTimeout 的回调完成”

我想出了一个解决方法,即恢复真实计时器并在断言之前等待 0 毫秒。

test('fake timers and restore real timers', async () => {
    jest.useFakeTimers();

    const firstAsyncFunction = jest.fn();
    const secondAsyncFunction = jest.fn();
    const thirdAsyncFunction = jest.fn();

    startTimerWithAsyncCallback(
        firstAsyncFunction,
        secondAsyncFunction,
        thirdAsyncFunction,
        1000,
    );
    jest.advanceTimersByTime(2000);

    expect(firstAsyncFunction).toHaveBeenCalled();
    await waitAsyncFunctionsToComplete();             // WORKAROUND
    expect(secondAsyncFunction).toHaveBeenCalled();
    expect(thirdAsyncFunction).toHaveBeenCalled();
});

async function waitAsyncFunctionsToComplete() {
    jest.useRealTimers();
    await delay(0);
    jest.useFakeTimers();
}

async function delay(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

有没有更合适的方法来实现这一点?

【问题讨论】:

  • 不方便测试的原因是startTimerWithAsyncCallback() 无法知道它何时完成。如果这个函数返回一个你可以用来知道它何时完成的承诺会更简洁。
  • @MarkMeyer 我明白你的意思。然而,在我最初的问题中,我使用setInterval 而不是setTimeout 来定期运行asyncCallback。也许我应该重新考虑我的设计......

标签: javascript node.js unit-testing jestjs


【解决方案1】:

正如 Mark Meyer 在 cmets 中所建议的,让 startTimerWithAsyncCallback 返回一个 Promise 更方便测试

function startTimerWithAsyncCallback(
    firstAsyncFunction,
    secondAsyncFunction,
    thirdAsyncFunction,
    millis,
) {
    return new Promise((resolve) => {      // <==
        setTimeout(async () => {
            await firstAsyncFunction();
            await secondAsyncFunction();
            await thirdAsyncFunction();
            resolve();                     // <==
        }, millis);
    });
}


describe('Using async callbacks with timers', () => {
    test('fake timers', async () => {
        jest.useFakeTimers();

        const firstAsyncFunction = jest.fn();
        const secondAsyncFunction = jest.fn();
        const thirdAsyncFunction = jest.fn();

        const promise = startTimerWithAsyncCallback( // <==
            firstAsyncFunction,
            secondAsyncFunction,
            thirdAsyncFunction,
            1000,
        );
        jest.advanceTimersByTime(2000);

        await promise;  <==

        expect(firstAsyncFunction).toHaveBeenCalled();
        expect(secondAsyncFunction).toHaveBeenCalled();
        expect(thirdAsyncFunction).toHaveBeenCalled();
    });
});

【讨论】:

    【解决方案2】:

    我只想使用@sinonjs/fake-timers 而不是jest.useFakeTimers()

    看起来他们只是放弃了整件事。

    如果你将“modern”作为参数传递,@sinonjs/fake-timers 将被使用 作为实现而不是 Jest 自己的假计时器。这也嘲笑 额外的计时器,如日期。 “现代”将是默认行为 开玩笑 27. https://jestjs.io/docs/en/jest-object#mock-timers

    【讨论】:

      猜你喜欢
      • 2021-07-31
      • 1970-01-01
      • 2015-09-03
      • 1970-01-01
      • 2018-04-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-21
      相关资源
      最近更新 更多