How do I test a recursive, asynchronous JavaScript function using fake timers? - javascript

I have a basic recursive function, which works correctly when executed. I would like to use Sinon's fake timers to test the function at each stage of execution.
However, it seems that the fake timers are only applying to the first call of the recursive function.
I'm curious if anyone can help figure out how to get the fake timers working all the way through.
Example:
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
async function ensureCount(count, { attempt, delay }) {
attempt = attempt || 0
console.log('Attempt', attempt)
if (attempt === count) {
return
}
await wait(delay)
await ensureCount(count, { attempt: attempt + 1, delay })
}
Tested like so (with the help of this article):
it('retries after a given delay', function() {
const clock = sinon.useFakeTimers()
const promise = ensureCount(2, { delay: 200 })
clock.tick(200)
// Make some assertions.
clock.tick(200)
// Make some assertions.
clock.tick(200)
// Make some assertions.
return promise
})
The expected console output (which is what happens without fake timers):
Attempt 0
Attempt 1
Attempt 2
✔ retries after a given delay (405ms)
The actual console output (which happens with fake timers):
Attempt 0
Attempt 1
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
The Question:
Is there a way to get the fake timers to apply to every step of the recursive call, rather than just the first one?

Turns out Sinon provides a .tickAsync() function for this that I hadn't seen. (Thanks to this comment).
This code resolves the issue:
it('retries after a given delay', async () => {
const clock = sinon.useFakeTimers()
ensureCount(2, { delay: 200 })
await clock.tickAsync(200)
// Make some assertions.
await clock.tickAsync(200)
// Make some assertions.
await clock.tickAsync(200)
// Make some assertions.
clock.restore()
})

Related

Better way to wait for a function until next step javascript

I am writing a browser-based javascript to scrape the input value by user and provide feedback. And in the page, you have to click on different areas for the desired element to show. I am trying to see if there is a way I can get javascript to wait until the element returns before I start going to the next one. Right now I just use setTimeout, but I have to loop all the elements I want to get, so nasty nested functions. trying to see if there is a more elegant solution. I used to write VBA, so I am not so familiar with async web API. still learning
function a(){
document.getElementById('1').click()
setTimeout(function(){document.getElementById('1a');addmessage;b;}),5000}
}
function b(){
document.getElementById('2').click()
setTimeout(function(){document.getElementById('2a');addmessage;c;}),5000}
}
function c(){
document.getElementById('3').click()
setTimeout(function(){document.getElementById('3a');addmessage;d;}),5000}
}
function addmessage(){
//this is the function I used to add element do some operation and add to dialog box
}
etc..
what I imagined is like below
function addmessage(element_to_click,element_to_collect){
// click element_to_click
// wait 5s
// collect innertext from element_to_collect
// do some operation, generate a message
//add message to dialog box
}
so I can do something like
addmessage(1,1a)
addmessage(2,2a) <--- this won't execute until the first is complete.
If I'm understanding the question correctly, you can start with a promise wrapper around setTimeout:
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
Then addMessage could be:
async function addMessage(element_to_click, element_to_collect) {
element_to_click.click();
await delay(5000);
return element_to_collect.textContent;
}
You didn't say what you wanted to do with the text, so I've just had it return it.
Then you can either write a loop or a series of calls:
async function run() {
const a = await addMessage(
document.getElementById("1"),
document.getElementById("???"),
);
const b = await addMessage(
document.getElementById("2"),
document.getElementById("???"),
);
const c = await addMessage(
document.getElementById("2"),
document.getElementById("???"),
);
// ...do something with `a`, `b`, and `c`...
}
When calling run, don't forget to handle promise rejection (in case something goes wrong).
You've said that after the click, "...the page needs a few seconds to load..." Hardcoded timeouts are fragile and often break. Instead of a timeout, try to hook into something that will change, perhaps via a MutationObserver on the element you're "collecting" from.
You can make your code wait for a timeout to complete by wrapping it in a Promise and using the async await pattern:
// Return a new Promise immediately, that completes at the end of the timeout
function addmessage(element_to_click, element_to_collect) {
return new Promise((resolve, reject) => {
// do whatever
// resolve the Promise after 5s
setTimeout(resolve, 5000)
// you can also resolve the Promise on any other event, like user input
// (or use `reject` when an error occurs)
// you can pass a return value to `resolve` like resolve(input.value)
})
}
// `async` functions also return a Promise immediately
async function addMessages() {
// `await` will block until Promise completes and
// returns the resolved value to be used synchronously within the async func
const in1 = await addmessage('1', '1a')
const in2 = await addmessage('2', '2a')
const in3 = await addmessage('3', '3a')
}
addMessages()

Jest "Async callback was not invoked within the 5000 ms timeout" with monkey-patched `test` and useFakeTimers

This setup is extremely specific but I couldn't find any similar resources online so I'm posting here in case it helps anyone.
There are many questions about Jest and Async callback was not invoked, but I haven't found any questions whose root issue revolves around the use of jest.useFakeTimers(). My function should take no time to execute when using fake timers, but for some reason Jest is hanging.
I'm using Jest 26 so I'm manually specifying to use modern timers.
This is a complete code snippet that demonstrates the issue.
jest.useFakeTimers('modern')
let setTimeoutSpy = jest.spyOn(global, 'setTimeout')
async function retryThrowable(
fn,
maxRetries = 5,
currentAttempt = 0
) {
try {
return await fn()
} catch (e) {
if (currentAttempt < maxRetries) {
setTimeout(
() => retryThrowable(fn, maxRetries, currentAttempt + 1),
1 * Math.pow(1, currentAttempt)
)
}
throw e
}
}
describe('retryThrowable', () => {
const fnErr = jest.fn(async () => { throw new Error('err') })
it('retries `maxRetries` times if result is Err', async () => {
jest.clearAllMocks()
const maxRetries = 5
await expect(retryThrowable(() => fnErr(), maxRetries)).rejects.toThrow('err')
for (let _ in Array(maxRetries).fill(0)) {
jest.runAllTimers()
await Promise.resolve() // https://stackoverflow.com/a/52196951/3991555
}
expect(setTimeoutSpy).toHaveBeenCalledTimes(maxRetries)
})
})
The full error message is
Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error: Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.
at mapper (../../node_modules/jest-jasmine2/build/queueRunner.js:27:45)
Any ideas would be very appreciated
edit 1: I have tried --detectOpenHandles but no new information is provided
edit 2: I just tried my above code snippet in a fresh project and realized that it passes just fine. So the issue must somewhere else in my Jest config. I'll answer my own question when I determine the root cause
My issue ended up being in my jest configuration.
We execute tests directly against an in-memory DB, and to keep our tests clean we wrap each test in a DB transaction. Jest doesn't provide a native aroundEach hook like many other test runners, so we achieved this by monkey-patching the global test and it functions so we could execute the test callback inside a transaction. Not sure if it matters but to be explicit we are using Sequelize as our ORM and for transactions.
The test I was executing (as seen above) recursively called setTimeout with a function which threw an error / rejected a Promise. Sequelize transactions apparently do not appreciate unhandled rejections, and it was causing the test to hang. I never was able to get to the root of why the test was hanging; the transaction successfully rolled back and all test expectations were run, but for some reason the test never exited.
Solution #1 (not great)
My first solution is not pretty but it is pragmatic. I simply extended the Jest test function with a variant which does not use the monkey-patched test.
// jest.setup.ts
declare namespace jest {
interface It {
noDb: (name: string, fn?: ProvidesCallback, timeout?: number) => void
}
}
it.noDb = it
// jest.config.js
module.exports = {
// ...
setupFilesAfterEnv: [
'./jest.setup.ts', // <-- inject `it.noDb` method
'./jest.mokey-patch.ts', // <-- monkey-patching
],
}
Then, I modified the test from the OP to call this new function
it.noDb('retries `maxRetries` times if result is Err', ...
More details on how and why this extension works can be found in this blog post.
Solution #2 (better)
After messing with this more, I realized that the root issue was that there were unhandled promise rejections happening in the main thread. I'm not sure why this conflicted with Sequelize Transactions but suffice to say it's bad practice anyway.
I was able to avoid the issue entirely, as well as any bizarre Jest extensions, by simply fixing the method to only throw on the first call. This way, we can handle errors when we call retryThrowable but do not throw errors on subsequent calls.
// ...
try {
return await fn()
} catch (e) {
if (currentAttempt < maxRetries) {
setTimeout(
() => retryThrowable(fn, maxRetries, currentAttempt + 1),
1 * Math.pow(1, currentAttempt)
)
}
// 💡 this is the new part
if (currentAttempt === 0) {
throw e
}
}
// ...

Async / await vs then which is the best for performance?

I have a simple code in JavaScript that execute a request in an API and return the response, simple. But in this case I will have thousands of requests. So, which one of the code options will perform better, and why. Also which one is recommended as good pratices these days?
First options is using the .then to resolve the promises and the seccond one is using async / await.
In my tests the two options had very similar results without significant differences, but I'm not sure in scale.
// Using then
doSomething(payload) {
const url = 'https://link-here/consultas';
return this.axios.get(url, {
params: {
token: payload.token,
chave: payload.chave,
},
}).then(resp => resp.data);
}
// Using Async / await
async doSomething(payload) {
const url = 'https://link-here/consultas';
const resp = await this.axios.get(url, {
params: {
token: payload.token,
chave: payload.chave,
},
});
return resp.data;
}
Any explanation will be of great value.
From a performance point of view, await is just an internal version of .then() (doing basically the same thing). The reason to choose one over the other doesn't really have to do with performance, but has to do with desired coding style or coding convenience. Certainly, the interpreter has a few more opportunities to optimize things internally with await, but its unlikely that should be how you decide which to use. If all else was equal, I would choose await for the reason cited above. But, I'd first choose which made the code simpler to write and understand and maintain and test.
Used properly, await can often save you a bunch of lines of code making your code simpler to read, test and maintain. That's why it was invented.
There's no meaningful difference between the two versions of your code. Both achieve the same result when the axios call is successful or has an error.
Where await could make more of a convenience difference is if you had multiple successive asynchronous calls that needed to be serialized. Then, rather than bracketing them each inside a .then() handler to chain them properly, you could just use await and have simpler looking code.
A common mistake with both await and .then() is to forget proper error handling. If your error handling desire in this function is to just return the rejected promise, then both of your versions do that identically. But, if you have multiple async calls in a row and you want to do anything more complex than just returning the first rejection, then the error handling techniques for await and .then()/.catch() are quite different and which seems simpler will depend upon the situation.
There should be some corrections in this thread. await and .then are going to give very different results, and should be used for different reasons.
await will WAIT for something, and then continue to the next line. It's also the simpler of the two because it behaves mechanically more like synchronous behavior. You do step #1, wait, and then continue.
console.log("Runs first.");
await SomeFunction();
console.log("Runs last.");
.then splits off from the original call and starts operating within its own scope, and will update at a time the original scope cannot predict. If we can put semantics aside for a moment, it's "more asynchronous," because it leaves the old scope and branches off into a new one.
console.log("Runs first.");
SomeFunction().then((value) => {console.log("Runs last (probably). Didn't use await on SomeFunction().")})
console.log("Runs second (probably).");
As more explanation to #user280209 answer let's consider the following function which returns promise and compare its execution with .then() and async await.
function call(timeout) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`This call took ${timeout} seconds`);
resolve(true);
}, timeout * 1000);
});
}
With .then()
(async () => {
call(5).then((r) => {
console.log(r);
});
await call(2); //This will print result first
await call(1);
})();
When running the above call the logs will be
This call took 2 seconds
This call took 1 seconds
This call took 5 seconds
true
As we can see .then() didn't pause the execution of its below line until it completes.
With async/wait
(async () => {
await call(5); //This will print result first
await call(2);
await call(1);
})();
When run the above function logs will be
This call took 5 seconds
This call took 2 seconds
This call took 1 seconds
So I think if your promise's result won't be used in the following lines, .then() may be better.
For those saying await blocks the code until the async call returns you are missing the point. "await" is syntactic sugar for a promise.then(). It is effectively wrapping the rest of your function in the then block of a promise it is creating for you. There is no real "blocking" or "waiting".
run();
async function run() {
console.log('running');
makePromises();
console.log('exiting right away!');
}
async function makePromises() {
console.log('make promise 1');
const myPromise = promiseMe(1)
.then(msg => {
console.log(`What i want to run after the promise is resolved ${msg}`)
})
console.log('make promise 2')
const msg = await promiseMe(2);
console.log(`What i want to run after the promise is resolved via await ${msg}`)
}
function promiseMe(num: number): Promise<string> {
return new Promise((resolve, reject) => {
console.log(`promise`)
resolve(`hello promise ${num}`);
})
}
The await line in makePromises does not block anything and the output is:
running
make promise 1
promise
make promise 2
promise
exiting right away!
What i want to run after the promise is resolved hello promise 1
What i want to run after the promise is resolved via await hello promise 2
Actually.
Await/Async can perform more efficiently as Promise.then() loses the scope in which it was called after execution, you are attaching a callback to the callback stack.
What it causes is: The system now has to store a reference to where the .then() was called. In case of error it has to correctly point to where the error happens, otherwise, without the scope (as the system resumed execution after called the Promise, waiting to comeback to the .then() later) it isn't able to point to where the error happened.
Async/Await you suspend the exection of the method where it is being called thus preserving reference.
If we just consider performance(time taken) then it actually depends on whether your operations are serial or parallel. If your tasks are serial then there will be no difference between await and .then. But if your tasks are parallel then .then will take less time. Consider the following example
let time = Date.now();
// first task takes 1.5 secs
async function firstTask () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
},1500)
})
}
// second task takes 2 secs
async function secondTask () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
},2000)
})
}
// using await
async function call(){
const d1 = await firstTask();
const d2 = await secondTask();
console.log(Date.now()-time, d1+d2)
}
call()
// using .then
async function call2(){
let d1=null,d2=null;
firstTask().then(data => {
d1=data;
if(d2){
console.log(Date.now()-time, d1+d2);
}
})
secondTask().then(data => {
d2=data;
if(d1){
console.log(Date.now()-time, d1+d2);
}
})
}
call2()
Here are the two tasks, first takes 1.5 secs and second takes 2 secs. Call function uses await where as call2 function uses .then . The output is as follows
From call2 2012 3
From call 3506 3
I hope it helps.
As far as I understand .then() and await are not the same thing. An async function won't proceed with the next command until the promise is resolved/rejected since it's basically an implementation of generators. On the contrast, in the case of .then(), the execution of the function will proceed with the next command and the resolve/reject callback will be executed "when there's time" aka when the current event loop (not entirely sure about that part) will be completed.
tldr; on a single promise await and .then() behave similarly but when one promise needs another one to be resolved first then the two of them behave entirely different
Many answer have been provided to this question already. However, to point out key information in the answers above and from my understanding, note below point:
only use await when not handling error return
if no crucial need for error handling use await instead
use .then .catch if returned error message or data is crucial for debugging / or proper error handling instead of try catch for await
Choose any prefer method from code sample below
const getData = (params = {name: 'john', email: 'ex#gmail.com'}) => {
return axios.post(url, params);
}
// anywhere you want to get the return data
// using await
const setData = async () => {
const data = await getData();
}
// to handle error with await
const setData = async () => {
try {
const data = await getData();
}
catch(err) {
console.log(err.message);
}
}
// using .then .catch
const setData = () => {
var data;
getData().then((res) => {
data = res.data; console.log(data)
}).catch((err) => {
console.log(err.message);
});
}

Unit testing NodeJS Promise inside a function

Im trying to unit test a function that calls a promise...
Using Mocha, Sinon. I have a functional block like this:
myfile.js:
let OuterDependecy = require('mydep');
function TestFunction(callback) {
OuterDependency.PromiseFunction().then(response => {
//some logic here
}).catch(err => {callback(err)});
inside my test i have used proxyquire to mock the outerdependecy
testfile.js
let proxyquire = require('proxyquire');
let OuterDepStub = {};
let testingFunc = proxyquire('myfile.js', {'mydep': OuterDepStub});
... then inside my testing block
let stubCallback = function() {
console.log('Stub dubadub dub'); //note...i can use sinon.spy here instead
};
beforeEach(()=>{
OuterDependency.PromiseFunction = function(arg) {
return new Promise((resolve, reject)=>{
reject('BAD');
});
};
spy = sinon.spy(stubCallback);
});
my actual test now calls the main "testfunction"
it('Catches Errors, and calls back using error', done => {
TestFunction(stubCallback);
expect(spy).to.have.been.called;
done();
});
I see the stub being called (the console log, hence why i didnt want to use sinon.spy) but the spy is saying its not called. and unit test fails.
I believe this is probably due to a race condition of sorts where the promise is resolving after my test is run... is there anyway to delay the test until my promise is resolve.
i know in in angularjs promise testing, there was a way to "tick" the promise so it resolves when you want to. possible in nodejs?
is there anyway to delay the test until my promise is resolve.
As far as I understand your issue, yes, you should only call done() after the promise is settled. In order to do that,you need two things:
1- Enforce TestFunction to return a Promise, so you can wait until it resolves:
function TestFunction(callback) {
return OuterDependency.PromiseFunction().then(response => {
//some logic here
}).catch(err => { callback(err) });
}
2- Wait to that promise to settle, then call done.
it('Catches Errors, and calls back using error', done => {
TestFunction(stubCallback).then(() => {
expect(spy).to.have.been.called;
done();
})
});
now, our then block won't run until the catch block within TestFunction, so if the test works as expected (i.e. the catch block fires and the callback gets fired), the expectation and the done calls will always fire after the callback gets called.
I see the stub being called (the console log, hence why i didnt want to use sinon.spy) but the spy is saying its not called. and unit test fails.
That's because your expectation runs right after the TestFunction calls, without waiting for it to settle. However, it will get called lately, thus the console.log appears in the next spec.

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

Categories