Sinon useFakeTimers mocking delay promise times out - javascript

I implemented a non-blocking delay using Promise and setTimeout as follows:
await new Promise((resolve) => setTimeout(resolve, 200))
I have a function which calls this delay:
const fn1 = async (v) => {
console.log(v)
}
const fn2 = async () => {
await fn1(1111111)
await new Promise((resolve) => setTimeout(resolve, 200))
await fn1(2222222)
}
I then tried to use useFakeTimers from Sinon to mock the delay in a unit test (Mocha):
const sinon = require('sinon')
describe('Test 1', () => {
it('Test 1.1', async () => {
const sb = sinon.createSandbox()
sb.useFakeTimers()
await fn2()
sb.clock.tick(500)
expect(1).to.equal(1)
sb.clock.restore()
})
})
I expected sb.clock.tick(500) to mock time passing for 500ms such that the delay promise (await new Promise((resolve) => setTimeout(resolve, 200))) would resolve, 2222222 would be printed, and the test would pass.
However, I got the following error:
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
I'm not sure if my understanding of useFakeTimers is wrong...

It seems the correct solution for this issue is to use clock.tickAsync.
I updated the target function a little bit for it to be more testable:
const fn = async (callback) => {
await callback(1111111)
await new Promise((resolve) => setTimeout(resolve, 200))
await callback(2222222)
}
And the working test is as follows:
describe('Test 1', () => {
let sb
beforeEach(() => {
sb = sinon.createSandbox()
sb.useFakeTimers()
})
afterEach(() => {
sb.clock.restore()
})
it('Test 1.1', async () => {
const callbackSpy = sinon.spy()
fn(callbackSpy)
expect(callbackSpy.callCount).to.equal(1)
await sb.clock.tickAsync(210)
expect(callbackSpy.callCount).to.equal(2)
expect(callbackSpy.calledWith(1111111)).to.equal(true)
expect(callbackSpy.calledWith(2222222)).to.equal(true)
})
})
And problem is that clock.tickAsync is only available in later versions of sinon.
For some earlier versions (e.g. 2.4), clock.tickAsync doesn't exist and I haven't figured out how to test the case above.

Related

How to promise.race an array of async/sync callbacks vs a timeout when both are ran in parallel

I have a set of callbacks that may run on different durations before I close my web app. I also have a timeout where if it reaches past the timeout duration, I also close the application. The reason for this is to prevent the callbacks from blocking in closing the web app if it passes timeout duration.
Here is my current solution:
public close() {
const callbacks = this.onBeforeCloseCallbacks.map((cb) => new Promise(res => res(cb())));
const timeout = new Promise((res) => setTimeout(res, TIMEOUT_DURATION));
await Promise.race([Promise.all(callbacks), timeout]).then((value) => {
// Currently returns Promise.all(callbacks) right away
console.log(value)
});
await this.pluginEngine.close();
}
}
These are my tests
it('Should still close the plugin when timing out', async () => {
// Arrange
const cleanupMock = jest.fn();
const cb1 = jest.fn().mockReturnValue(async () => new Promise(resolve => setTimeout(() => resolve(console.log('cb1')), 3000)));
const cleanupMock2 = jest.fn();
const cb2 = jest.fn().mockReturnValue(async () => new Promise(resolve => setTimeout(() => resolve(console.log('cb2')), 11000)));
const placementCloseService = new PlacementCloseService(integrationMock, pluginInterface);
// Act
// onBeforeClose is registering callbacks that needs to be run before close
placementCloseService.onBeforeClose(cb1);
placementCloseService.onBeforeClose(cb2);
await placementCloseService.close();
// Assert
expect(cleanupMock).toBeCalled();
expect(cleanupMock2).not.toBeCalled();
expect(pluginInterface.context.close).toBeCalled();
});
My current solution is returning Promise.all(callbacks) even if it hasn't called expected callbacks to run yet. What I expect to happen is that it passes through my timeout instead since it has a timer of 4000 and the last closeCallback has a timer of 5000.
What am I doing wrong?
Your closeCallbacks are not async, You need them to return a promise.
const closeCallbacks = [
// for sample purposes. i assigned timeouts to mock that it takes longer to run these callbacks then my timeout duration
async () => new Promise(resolve => setTimeout(() => resolve(console.log('cb1')), 3000)),
async () => new Promise(resolve => setTimeout(() => resolve(console.log('cb2')), 5000)),
];
This creates a parameter named async not an async function
(async) => {}
Your timeout function never calls res parameter
const wait = (cb, time) => new Promise(r => { cb(); r() }, time)
const closeCallbacks = [
() => wait(() => console.log('cb1'), 3000),
() => wait(() => console.log('cb2'), 5000)
];
const timeout = new Promise((res) => setTimeout(() => console.log('timeout'); res(), 4000));

How to wait for a function for a certain delay before executing the next function

Lets say i have two function i need to run
await func1()
await func2()
I want to wait for a maximum of 1000ms for func1() to complete. If it does not complete within the 1000ms then call func2(), don't need to cancel the request for func1()
But if the func1() returns in less than 1000ms, let's say 500ms, then it immediately executes the func2()
I thought of trying some custom sleep function but not sure on how I would exit the sleep before hand.
You can use Promise.race() to clamp the execution time. It accepts multiple promises and returns a single one that will resolve as soon as the first of the promise arguments resolves:
const sleep = ms =>
new Promise(resolve => setTimeout(resolve, ms));
//mock functions taking time
const func1 = () => sleep(100); // 0.100s
const func2 = () => sleep(2000); // 2s
const func3 = () => sleep(300); // 0.300s
//make a function that will clamp the wait time
const maxWait = ms => promise =>
Promise.race([sleep(ms), promise]);
async function main() {
const noMoreThan1Second = maxWait(1000);
console.time("func1");
await noMoreThan1Second(func1());
console.timeEnd("func1");
console.time("func2");
await noMoreThan1Second(func2());
console.timeEnd("func2");
console.time("func3");
await noMoreThan1Second(func3());
console.timeEnd("func3");
}
main();
Note that Promise.race() will not cancel the task that the slower promise(s) are linked to. Since promises do not control asynchronous tasks, they are simply a notification mechanism. Therefore, using Promise.race() simply means ignoring the results of the slower promises. The async task they do would still continue in the background and can still succeed or fail. If rejections need to be handled, then nothing changes with Promise.race():
const sleep = ms =>
new Promise(resolve => setTimeout(resolve, ms));
async function func() {
console.log("func() started");
await sleep(2000);
console.log("func() finished");
}
async function main() {
console.log("calling func()");
await Promise.race([sleep(1000), func()]);
console.log("finished waiting for func()");
}
main();
As a more advanced usage, default values might be returned if a promise is not resolved in time:
//allow optional value to be delayed
const sleep = (ms, value) =>
new Promise(resolve => setTimeout(resolve, ms, value));
const maxWait = (ms, value) => promise =>
Promise.race([sleep(ms, value), promise]);
const func1 = () => sleep(100, "one");
const func2 = () => sleep(2000, "two");
async function main() {
const noMoreThan1Second = maxWait(1000, "default");
console.log(await noMoreThan1Second(func1()));
console.log(await noMoreThan1Second(func2()));
}
main();
Alternatively, there could be an error if something takes too long:
//allow optional value to be delayed
const sleep = (ms, value) =>
new Promise(resolve => setTimeout(resolve, ms, value));
const delayedReject = (ms, value) =>
new Promise((_, reject) => setTimeout(reject, ms, value));
const maxWait = (ms, value) => promise =>
Promise.race([delayedReject(ms, value), promise]);
const func1 = () => sleep(100, "one");
const func2 = () => sleep(2000, "two");
async function main() {
const noMoreThan1Second = maxWait(1000, "timeout");
try {
console.log(await noMoreThan1Second(func1()));
console.log(await noMoreThan1Second(func2()));
} catch(e) {
console.log("problem encountered:", e)
}
}
main();
As a small note, an alternative implementation of delayedReject() that re-uses sleep() would be:
const delayedReject = (ms, value) =>
sleep(ms)
.then(() => Promise.reject(value));
or shorter eta-reduced version:
const delayedReject = (ms, value) =>
sleep(ms, value)
.then(Promise.reject);
You can use Promise.race function for doing this. Here is an example
const func1 = new Promise(resolve => {
setTimeout(() => {
console.log("func1");
resolve("func 1")
}, 1200);
})
const func2 = () => {console.log("func2")};
const promiseMaximumWait = (promise, time) => {
const controlPromise = new Promise((resolve) => {
setTimeout(() => resolve("controlPromise"), time)
});
return Promise.race([controlPromise, promise]);
}
promiseMaximumWait(func1, 1000)
.then((val) => {
console.log(val);
func2();
})
From MDN Docs
The Promise.race() method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise.
So we have created a control promise which is a settimeout whose duration can be set by ourself and will act as the maximum time after which we have to move ahead. If the func1 resolves in that time, then we proceed with result from func1 and execute func2 immediately. If the maximum time has elapsed we take the result from controlPromise and execute func2.

Why is a mocked jest promise not rejected with allSettled?

I want to test a method which returns the result of Promise.allSettled() and calls another function which returns promises.
I reduced the problem to the following test code:
describe('Promise tests', () => {
it('should reject directly', async () => {
const f = jest.fn().mockRejectedValue(new Error('foo'));
const p = async () => await f();
// works
await expect(p).rejects.toThrow('foo');
});
it('should reject with allSettled', async () => {
const f = jest.fn().mockRejectedValue(new Error('foo'));
const p = async () => await f();
const results = await Promise.allSettled([p]);
expect(results[0].status).toBe('rejected'); // fulfilled - but why?
expect(results[0].reason).toBe('foo');
});
});
Why is the second case not receiving a rejected promise?
node.js v14.3.0
jest v25.4.0
You are almost there. Promise.allSettled expects to receive an array of Promises not an array of functions returning a promise, which in fact is what your constant p does.
By simply calling the p() you solve your issue:
describe('Promise tests', () => {
it('should reject directly', async () => {
const f = jest.fn().mockRejectedValue(new Error('foo'));
const p = async () => await f();
// works
await expect(p()).rejects.toThrow('foo');
});
it('should reject with allSettled', async () => {
const f = jest.fn().mockRejectedValue(new Error('foo'));
const p = async () => await f();
const results = await Promise.allSettled([p()]);
expect(results[0].status).toBe('rejected'); // fulfilled - but why?
expect(results[0].reason).toBe('foo');
});
});
By the way: My linter complains about unnecessary awaits :-)

Jest with async function calls before and after setTimeout

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

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