Jest with async function calls before and after setTimeout

Viewed 4436

I have a main function which calls two async functions with sleep function in between. This is a basic example:

index.js

const func1 = async() => {
    setTimeout(()=>{console.log('func 1...')}, 1000);
}

const func2 = async() => {
    setTimeout(()=>{console.log('func 2...')}, 1000);
}

const sleep = ms => {
    console.log(`Sleeping for ${ms/1000} seconds`);
    return new Promise(resolve => {
        setTimeout(resolve, ms);
    })
}

const main = async() => {
    try {
    await func1();
    // Sleeping for a long long time
    console.log('Before Sleep');
    await sleep(2000000);
    console.log('After Sleep')
    await func2();
    return 'success';
    } catch(err) {
        console.log(err);
        return 'error'
    }
}

And this is my test code:

index.test.js

const index = require('./index');

jest.useFakeTimers();

describe('Testing index.js...', () => {
    test('Should return success', async() => {
        const promise = index();
        jest.advanceTimersByTime(2000000);
        promise.then(response => {
            expect(response).toBe('success');
        }) 
    });
})

The test passes, but the console shows the following:

func 1...
Before Sleep
Sleeping for 2000 seconds

I tried the same this, but with func1() and func2() being synchronous functions:

const func1 = () => {
    console.log('func 1...');
}

const func2 = () => {
    console.log('func 2...');

}

const sleep = ms => {
    // Sleeping for a long time
    console.log(`Sleeping for ${ms/1000} seconds`);
    return new Promise(resolve => {
        setTimeout(resolve, ms);
    })
}

const main = async() => {
    try {
    func1();
    // Sleeping for a long long time
    console.log('Before Sleep');
    await sleep(2000000);
    console.log('After Sleep')
    func2();
    return 'success';
    } catch(err) {
        console.log(err);
        return 'error'
    }
}

In that case, the test passes and the logs are also as expected:

func 1...
Before Sleep
Sleeping for 2000 seconds
After Sleep
func 2...

In the same synchronous code, if I make func1 async (keeping func2 synchronous), the problem reappears. If func1 is synchronous and func2 is async, everything works as expected.

I have also tried using jest.runAllTimers() and jest.runOnlyPendingTimers(). I have also tried using async-await in the test file, but that (understandably) gives a timeout error:

index.test.js using async-await

const index = require('./index');

jest.useFakeTimers();

describe('Testing index.js...', () => {
    test('Should return success', async() => {
        const promise = index();
        jest.advanceTimersByTime(3000000);
        const response = await promise;
        expect(response).toBe('success');  
    });
})

Console:

func 1...
Before Sleep
Sleeping for 2000 seconds

Error:

Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Timeout

How can I make this work? I have gone through and tried a solutions on lot of Github issues in jest, and also a lot of questions on stack overflow, none of the solutions seem to work.

I am using jest 25.5.4

Edit: I also tried increasing the value in jest.advanceTimersBytTime() to a day. And also tried making the function in describe async.

2 Answers

I've had a similar issue recently, what worked for me is to advance the timers from within an async-call. Seems like jest does not support setting the timers within a promise (see https://github.com/facebook/jest/pull/5171#issuecomment-528752754). Try doing:

describe('Testing index.js...', () => {
  it('Should return success', () => {    
    const promise = main();
    Promise.resolve().then(() => jest.advanceTimersByTime(2000005));
    return promise.then((res) => {
      expect(res).toBe('success');
    });

  });
});

async, raw promises and done callback shouldn't be used together in tests. This is a common sign that a developer isn't fully comfortable with Jest asynchronous testing, this leads to error-prone tests.

The problem with original is that promise.then(...) promise is ignored because it's not chained. Asynchronous test should return a promise in order for it to be chained.

func1 and func2 return promises that resolve immediately and produce one-tick delay rather than 1 second delay. This should be taken into account because otherwise there's a race condition with setTimeout being called after advanceTimersByTime.

It should be:

test('Should return success', async() => {
    const promise = index();
    await null; // match delay from await func1() 
    jest.advanceTimersByTime(2000000);
    const response = await promise;
    expect(response).toBe('success');
});
Related