useFakeTimers和异步回调

时间:2019-01-18 14:39:36

标签: javascript node.js unit-testing jestjs

考虑此功能

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

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

1 个答案:

答案 0 :(得分:0)

如Mark Meyer在评论中所建议,使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();
    });
});