使用Jest和setTimeout测试Promise

26

我正在尝试理解Jest的异步测试。

我的模块有一个函数,接受一个布尔值并返回一个值的Promise。执行者函数调用setTimeout,在超时回调中,承诺根据最初提供的布尔值解决或拒绝。代码看起来像这样:

const withPromises = (passes) => new Promise((resolve, reject) => {
    const act = () => {
    console.log(`in the timout callback, passed ${passes}`)
        if(passes) resolve('something')
        else reject(new Error('nothing'))
    }

    console.log('in the promise definition')

    setTimeout(act, 50)
})

export default { withPromises }

我想使用Jest进行测试。我猜我需要使用Jest提供的模拟计时器,所以我的测试脚本看起来有些像这样:

import { withPromises } from './request_something'

jest.useFakeTimers()

describe('using a promise and mock timers', () => {
    afterAll(() => {
        jest.runAllTimers()
    })


    test('gets a value, if conditions favor', () => {
        expect.assertions(1)
        return withPromises(true)
            .then(resolved => {
                expect(resolved).toBe('something')
            })
    })
})

无论我是否调用 jest.runAllTimers(),都会出现以下错误/测试失败。
Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

您能解释一下我做错了什么,以及我应该怎么做才能通过测试并按预期解决承诺吗?

2个回答

38
调用 jest.useFakeTimers() 可以将所有计时器函数都进行模拟,并且需要手动控制计时器的运行。你需要手动推进计时器,而不是自动运行。函数 jest.runTimersToTime(msToRun) 可以让你使计时器增加 msToRun 毫秒。通常情况下,你需要快进直到每个计时器都过期,但计算所有计时器完成所需的时间会很繁琐,因此 Jest 提供了 jest.runAllTimers() 来模拟足够的时间已经流逝。
你测试中的问题在于,在测试中你从未调用 jest.runAllTimers(),而你在 afterAll 钩子函数中调用它,该钩子函数在测试完成后才会被调用。在测试期间,计时器仍然保持为零,因此你的回调函数实际上永远不会被调用,Jest 会在预定的时间间隔(默认为5秒)后终止测试,以防止陷入可能无休止的测试。只有在测试超时后,你才调用 jest.runAllTimers(),此时它不起作用,因为所有测试已经完成。
你需要做的是启动 Promise,然后推进计时器。
describe('using a promise and mock timers', () => {
    test('gets a value, if conditions favor', () => {
        expect.assertions(1)
        // Keep a reference to the pending promise.
        const pendingPromise = withPromises(true)
            .then(resolved => {
                expect(resolved).toBe('something')
            })
        // Activate the timer (pretend the specified time has elapsed).
        jest.runAllTimers()
        // Return the promise, so Jest waits for its completion and fails the
        // test when the promise is rejected.
        return pendingPromise
    })
})

这个可行!非常感谢您的解释和代码示例。 - Simon Dell
5
好的回答!是否可以使用async / await语法编写此测试? - Jim

0
所以,我遇到了一个类似的问题,我想用setTimeout()来模拟一个返回promise的异步函数。但是我一直收到一个"超时错误"的提示。
在使用async await语法时,我遇到了一些问题。最后,我通过将"const x = await ..."拆分成两行,并在它们之间加入"jest.runAllTimers()"来解决了这个问题(你也可以在同一个方法中使用"jest.advanceTimersByTime()")。
下面是一个完整的测试,我在其中使用setTimeout来模拟一个promise:
it('test promise with set timeout', async () => {
    jest.useFakeTimers();
    const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

    const asyncFunction = async () => {
        await sleep(100);
        return true;
    };

    const resultPromise = asyncFunction();

    jest.runAllTimers();

    const result = await resultPromise;

    expect(result).toBe(true);
    jest.useRealTimers();
});

PS. 如果你有多个测试,最好将useFakeTimersuseRealTimers放在beforeEachafterEach块中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接