Jest with async function calls before and after setTimeout - javascript

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.

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

Await / Async with a conditional statement and IIAEF

Description
I want to conditionally call a db SELECT within a non-async function. I wrapped it in a IIAEF? like so
async function getScopedDataPolicies() {
const test = new Promise((res) => {
console.log('2')
res('test1')
})
return test;
}
function nonAsyncMethod() {
(async () => {
console.log('1')
const test = getScopedDataPolicies();
const policies = await test;
console.log('3')
})()
console.log('4')
}
nonAsyncMethod()
My expectation is
1
2
3
4
But I get
1
2
4
3
What wouldn't the await work in the immediately invoking method??
Look at these examples; am I missing something?
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/async_function
All await does is pause the execution of the current function, until an async task has finished, at which point execution will continue. It does not pause all of Javascript, that kind of behavior is synchronous programming, and Javascript is built to avoid it, because it makes for a really bad user experience if Javascript has to stall every time you make a rest request, etc.
update
Some possible workarounds:
await the IIFE, or take out the IIFE entirely. The IIFE is an async function, so it'll return a promise when it'scalled. You're not awaiting that promise, which is why your outer sync function continues on without waiting for it to finish.
async function nonAsyncMethod() {
await (async () => {
console.log('1')
const test = getScopedDataPolicies();
const policies = await test;
console.log('3')
})()
console.log('4')
}
Move console.log('4') into your IIFE. I know that console.log('4') might represent a whole log of code, but in some cases, you've just got to move it all inside - break up your function into smaller functions if you have to.
await inside the IIFE doesn't effect the outer function
To do so, you could either do this:
async function getScopedDataPolicies() {
const test = new Promise((res) => {
console.log('2')
res('test1')
})
return test;
}
async function nonAsyncMethod() {
await (async () => {
console.log('1')
const test = getScopedDataPolicies();
const policies = await test;
console.log('3')
})()
console.log('4')
}
nonAsyncMethod()
or this:
async function getScopedDataPolicies() {
const test = new Promise((res) => {
console.log('2')
res('test1')
})
return test;
}
function nonAsyncMethod() {
(async () => {
console.log('1')
const test = getScopedDataPolicies();
const policies = await test;
console.log('3')
})().then(() => {
console.log('4')
})
}
nonAsyncMethod()
Note: if you need to "wait" for nonAsyncMethod - you can only do await nonAsyncMethod(), or nonAsyncMethod.then(() => { continue here })

Mocha Dynamic test generation in before block not getting executed

As suggested in this post , I tried the steps to create dynamic tests , but I see the actual test(test.getMochaTest()in my below implementation) not getting executed. What's that I'm missing here, the call on test.getMochaTest() does not get executed in the before block.
describe('Dynamic Tests for Mocha', async () => {
let outcome ;
before(async() => {
await init().catch(() => console.error('Puppeteer environment initialization failed'));
return collectTests().then(async(collectTests) => {
console.info('4.Executing tests :');
describe('Dynamic test execution', async() => {
collectTests.forEach(async(test) => {
console.info(`\tModule under test : ${test.name}`);
// impl. of test.getMochaTest() DOES NOT get executed.
it(test.name, async() => outcome = await test.getMochaTest().catch(async () => {
console.error(`error while executing test:\t${test.name}`);
}));
});
}) ;
});
});
after(async () => {
console.info('5. Exiting tests...');
await HelperUtils.delay(10000).then(async () => { await browser.close(); });
console.log('executing after block');
});
it('placeholder', async() => {
await
console.log('place holder hack - skip it');
});
});
Array of tests is returned here :
async function collectTests():Promise<Array<ITest>> {
console.info('3.Collecting tests to execute ...');
testArray = new Array<ITest>();
const login:ITest = new SLogin('Login Check');
testArray.push(login);
return testArray;
}
The below implementation of getMochaTest in SLogin -> does not get executed .
export default class SLogin extends BTest implements ITest {
constructor(name: string) {
super(name);
}
async getMochaTest():Promise<Mocha.Func> {
return async () => {
console.log('Running Login check');
expect(true).to.equal(true);
};
}
}
It doesn't look like you're actually invoking the test.
Calling test.getMochaTest() only returns the async test function in a Promise, it doesn't execute it. So your catch block is catching errors while obtaining the function, not while executing it.
Breaking it out across multiple lines will hopefully make things clearer.
Here's what your code sample does. Notice it never executes the returned test function:
it(test.name, async () => {
const testFn = await test.getMochaTest().catch(() =>
console.error(`error while ***obtaining*** test: \t${test.name}`));
// oops - testFn never gets called!
});
And here's a corrected version where the test actually gets called:
it(test.name, async () => {
const testFn = await test.getMochaTest().catch(() =>
console.error(`error while ***obtaining*** test: \t${test.name}`));
const outcome = await testFn().catch(() =>
console.error(`error while ***executing*** test: \t${test.name}`));
});
Note: I wrote it that way with await and catch() to better match the format of your code sample. However, it's worth pointing out that it mixes async/await and Promise syntax. More idiomatic would be to catch errors with a try/catch block when using async/await.

stub setTimeout function

I have a function wait
async function wait(time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
And I call this wait like this: await wait(5000); from a different function.
I am writing unit test cases and it always executes wait and each test case waits for 5s.
How do I stub the setTimeout using Sinon?
I tried:
// Skip setTimeOut
clock = sinon.useFakeTimers({
now: Date.now(),
toFake: ['setTimeout']
});
await clock.tickAsync(4000);
await Promise.resolve();
But it didn't work.
Related post: setTimeout not triggered while using Sinon's fake timers
Github issue: https://github.com/sinonjs/fake-timers/issues/194#issuecomment-395224370
You can solve this in two ways.
Consider whether your test case requires a delay of 5000ms.
The unit test should test the code logic, it's not integration tests. So, maybe you just need to make an assertion check the wait function is to be called with parameter. It's enough. We don't need to wait for 5000ms delay in the test case.
If you insist want to use sinon.useFakeTimers() and clock.tick(5000).
From the related post, we can do it like this:
index.ts:
async function wait(time: number, clock?) {
return new Promise((resolve) => {
setTimeout(resolve, time);
clock && clock.tick(time);
});
}
export async function main(time, /* for testing */ clock?) {
await wait(time, clock);
console.log('main');
}
index.test.ts:
import { main } from './';
import sinon, { SinonFakeTimers } from 'sinon';
describe('60617715', () => {
let clock: SinonFakeTimers;
beforeEach(() => {
clock = sinon.useFakeTimers();
});
afterEach(() => {
clock.restore();
});
it('should pass', async () => {
await main(5000, clock);
});
});
unit test results:
60617715
main
✓ should pass
1 passing (9ms)

How to use JEST to test the time course of recursive functions

I write a test using JEST. I do not know how to test promise recursion in JEST.
In this test, the retry function that performs recursion is the target of the test until the promise is resolved.
export function retry<T>(fn: () => Promise<T>, limit: number = 5, interval: number = 1000): Promise<T> {
return new Promise((resolve, reject) => {
fn()
.then(resolve)
.catch((error) => {
setTimeout(() => {
// Reject if the upper limit number of retries is exceeded
if (limit === 1) {
reject(error);
return;
}
// Performs recursive processing of callbacks for which the upper limit number of retries has not been completed
try {
resolve(retry(fn, limit - 1, interval));
} catch (err) {
reject(err);
}
}, interval);
});
});
}
Perform the following test on the above retry function.
retry() is resolved in the third run. The first, second and third times are called every 1000 seconds respectively.
I thought it would be as follows when writing these in JEST.
jest.useFakeTimers();
describe('retry', () => {
// Timer initialization for each test
beforeEach(() => {
jest.clearAllTimers();
});
// Initialize timer after all tests
afterEach(() => {
jest.clearAllTimers();
});
test('resolve on the third call', async () => {
const fn = jest
.fn()
.mockRejectedValueOnce(new Error('Async error'))
.mockRejectedValueOnce(new Error('Async error'))
.mockResolvedValueOnce('resolve');
// Test not to be called
expect(fn).not.toBeCalled();
// Mock function call firs execution
await retry(fn);
// Advance Timer for 1000 ms and execute for the second time
jest.advanceTimersByTime(1000);
expect(fn).toHaveBeenCalledTimes(2);
// Advance Timer for 1000 ms and execute for the third time
jest.advanceTimersByTime(1000);
expect(fn).toHaveBeenCalledTimes(3);
await expect(fn).resolves.toBe('resolve');
});
});
As a result, it failed in the following error.
● retry › resolve on the third call
Timeout - Async callback was not invoked within the 30000ms timeout specified by jest.setTimeout.Error:
> 16 | test('resolve on the third call', async () => {
| ^
17 | jest.useFakeTimers();
18 | const fn = jest
19 | .fn()
I think that it will be manageable in the setting of JEST regarding this error. However, fundamentally, I do not know how to test promise recursive processing in JEST.
Your function is so hard to testing with timer.
When you call await retry(fn); this mean you will wait until retry return a value, but the setTimeout has been blocked until you call jest.advanceTimersByTime(1000); => this is main reason, because jest.advanceTimersByTime(1000); never been called.
You can see my example, it is working fine with jest's fake timers.
test("timing", async () => {
async function simpleTimer(callback) {
await callback();
setTimeout(() => {
simpleTimer(callback);
}, 1000);
}
const callback = jest.fn();
await simpleTimer(callback); // it does not block any things
for (let i = 0; i < 8; i++) {
jest.advanceTimersByTime(1000); // then, this line will be execute
await Promise.resolve(); // allow any pending jobs in the PromiseJobs queue to run
}
expect(callback).toHaveBeenCalledTimes(9); // SUCCESS
});
I think, you could skip test timer detail, just test about you logic: fn has been called 3 times, finally it return "resolve"
test("resolve on the third call", async () => {
const fn = jest
.fn()
.mockRejectedValueOnce(new Error("Async error"))
.mockRejectedValueOnce(new Error("Async error"))
.mockResolvedValueOnce("resolve");
// expect.assertions(3);
// Test not to be called
expect(fn).not.toBeCalled();
// Mock function call firs execution
const result = await retry(fn);
expect(result).toEqual("resolve");
expect(fn).toHaveBeenCalledTimes(3);
});
Note: remove all your fake timers - jest.useFakeTimers
The documentation of jest suggests that testing promises is fairly straightforward ( https://jestjs.io/docs/en/asynchronous )
They give this example (assume fetchData is returning a promise just like your retry function)
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
I agree with #hoangdv this recursive setTimeout pattern is very tricky to test. This is the only solution that worked for me after trying #hoangdv's (which gave me much inspiration).
Note I'm using a similar retry pattern around network Fetches. I'm using React Testing Library to look for an error message that only appears when the retry function ran 3 times.
it('Shows file upload error on timeout', async () => {
jest.useFakeTimers();
setupTest();
// sets up various conditions that trigger the underlying fetch
while (
screen.queryByText(
'There appears to be a network issue. Please try again in a few minutes.'
) === null
) {
jest.runOnlyPendingTimers(); // then, this line will be execute
await Promise.resolve(); // allow any pending jobs in the PromiseJobs queue to run
}
await waitFor(() => {
expect(
screen.getByText(
'There appears to be a network issue. Please try again in a few minutes.'
)
).toBeInTheDocument();
});
jest.useRealTimers();
});

Jest setTimeout not pausing test

it('has working hooks', async () => {
setTimeout(() => {
console.log("Why don't I run?")
expect(true).toBe(true)
}, 15000)
I've already reviewed this answer, Jest documentation, and several GitHub threads:
Disable Jest setTimeout mock
Right now, the function inside the timeout doesn't run.
How can I get Jest to pause its execution of the test for 15 seconds and then run the inner function?
Thanks!
it('has working hooks', async () => {
await new Promise(res => setTimeout(() => {
console.log("Why don't I run?")
expect(true).toBe(true)
res()
}, 15000))
})
or
it('has working hooks', done => {
setTimeout(() => {
console.log("Why don't I run?")
expect(true).toBe(true)
done()
}, 15000)
})
A nice and clean way to do it (without callbacks) we can simply run an await and pass the res to setTimeout(res, XXX) like so:
it('works with await Promise and setTimeout', async () => {
// await 15000ms before continuing further
await new Promise(res => setTimeout(res, 15000));
// run your test
expect(true).toBe(true)
});
setTimeout is now available through the jest object, and it will function as you expect: https://jestjs.io/docs/jest-object#misc.
it('works with jest.setTimeout', async () => {
// pause test example for 15 seconds
jest.setTimeout(15000)
// run your test
expect(true).toBe(true)
});
Note: If you attempt to set a timeout for 5000ms or more without the jest object, jest will error and let you know to use jest.setTimeout instead.

Categories

Resources