Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing a Promise using setTimeout with Jest

I'm trying to understand Jest's asynchronous testing.

My module has a function which accepts a boolean and returns a Promise of a value. The executer function calls setTimeout, and in the timed out callback the promise resolves or rejects depending on the boolean initially supplied. The code looks like this:

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 }

I'd like to test this using Jest. I guess that I need to use the mock timers Jest provides, so my test script looks a bit like this:

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

I get the following error/failed test, whether or not I call jest.runAllTimers()

Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.

Can you explain where I'm going wrong and what I might do to get a passing test that the promise resolves as expected?

like image 400
Simon Dell Avatar asked Oct 12 '17 13:10

Simon Dell


People also ask

Can I use setTimeout in Jest?

The native timer functions (i.e., setTimeout() , setInterval() , clearTimeout() , clearInterval() ) are less than ideal for a testing environment since they depend on real time to elapse. Jest can swap out timers with functions that allow you to control the passage of time.

How do you resolve a Promise in Jest?

You can also use the . resolves matcher in your expect statement, and Jest will wait for that promise to resolve. If the promise is rejected, the test will automatically fail. return expect(fetchData()).

How do I return setTimeout as a Promise?

To make a promise from setTimeout with JavaScript, we can use the Promise constructor. const later = (delay) => { return new Promise((resolve) => { setTimeout(resolve, delay); }); };

What does setTimeout do in a Promise?

Promise. resolve(1) is a static function that returns an immediately resolved promise. setTimeout(callback, 0) executes the callback with a delay of 0 milliseconds.


1 Answers

The call to jest.useFakeTimers() mocks every timer function with one that you must control. Instead of the timer running automatically, you would advance it manually. The jest.runTimersToTime(msToRun) function would advance it by msToRun milliseconds. It's very common that you want to fast-forward until every timer has elapsed and it would be cumbersome to calculate the time it takes for all the timers to finish, so Jest provides jest.runAllTimers(), which pretends that enough time has passed.

The problem in your test is that you never call jest.runAllTimers() in the test, but you call it in the afterAll hook, which is called after the tests have finished. During your test the timer remains at zero so your callback is never actually called and Jest aborts it after a predefined interval (default: 5s) to prevent being stuck with a potentially endless test. Only after the test has timed out, you call jest.runAllTimers(), at which point it doesn't do anything, since all tests have already finished.

What you need to do is launch the promise and then advance the timer.

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
    })
})
like image 55
Michael Jungo Avatar answered Sep 20 '22 15:09

Michael Jungo