Testing code with a universal catch with Jest - race condition - javascript

I just realized all of my test code has a race condition.
My style pattern follows something like this:
const myFunc = (callback) => {
return somePromise().then((result) => {
return someOtherPromise();
}).then((result) => {
db.end(() => {
callback();
});
}).catch((err) => {
db.end(() => {
callback(err);
});
});
};
I'm testing with Jest. Test code looks something like this.
it('should work', (done) => {
// mock stuff
let callback = () => {
expect(...);
done();
};
myFunc(callback);
});
I have dozens of functions and tests following this pattern. The last test I wrote was giving me a Jest matcher error in my callback. After much confusion, I realized that the first callback execution is throwing the Jest failure, and the callback with the err parameter is being executed and failing before done() is called by the first callback execution.
I'm realizing this pattern might be absolutely terrible. I've managed to beat the race condition by having certain expect() calls in certain order, but that's no way to do this.
How can I remove the potential for the race condition here?
I'm open to completely changing the style I do this. I know my Javascript isn't particularly amazing, and the system is still pretty early in its development.

My colleague advised me that this is a good case to use async/await.
See a new version of the code under test:
const myFunc = async (callback) => {
let other_result;
try {
let result = await somePromise();
other_result = await someOtherPromise(result);
} catch (err) {
db.end(() => {
callback(err);
});
return;
}
db.end(() => {
callback(null, other_result);
});
};
I updated things a bit to make it seem more real-world-ish.
I know this makes myFunc return a promise, but that's okay for my use-case. By doing this, I'm ensuring that the callback is only executed once, preventing the Jest error from getting caught up elsewhere.
EDIT:
I'm realizing that this is the same as if I had moved the catch block to be before the final then block, I would have the same behavior :/

Related

How to test calling to an async not-awaited function

I want to test the execution of both fuctionUT and an inner async unwaited function externalCall passed by injection. The following code is simple working example of my functions and their usage:
const sleep = async (ms) => new Promise( (accept) => setTimeout(() => accept(), ms) )
const callToExternaService = async () => sleep(1000)
const backgroundJob = async (externalCall) => {
await sleep(500) // Simulate in app work
await externalCall() // Simulate external call
console.log('bk job done')
return 'job done'
}
const appDeps = {
externalService: callToExternaService
}
const functionUT = async (deps) => {
await sleep(30) // Simulate func work
// await backgroundJob(deps.externalService) // This make test work but slow down functionUT execution
backgroundJob(deps.externalService) // I don't want to wait for performance reason
.then( () => console.log('bk job ok') )
.catch( () => console.log('bk job error') )
return 'done'
}
functionUT( appDeps )
.then( (result) => console.log(result) )
.catch( err => console.log(err) )
module.exports = {
functionUT
}
Here there is a simple jest test case that fail but just for timing reasons:
const { functionUT } = require('./index')
describe('test', () => {
it('should pass', async () => {
const externaServiceMock = jest.fn()
const fakeDeps = {
externalService: externaServiceMock
}
const result = await functionUT(fakeDeps)
expect(result).toBe('done')
expect(externaServiceMock).toBeCalledTimes(1) //Here fail but just for timing reasons
})
})
What is the correct way to test the calling of externaServiceMock (make the test pass) without slowdown the performance of the functionUT ?
I have already found similar requests, but they threat only a simplified version of the problem.
how to test an embedded async call
You can't test for the callToExternaService to be called "somewhen later" indeed.
You can however mock backgroundJob and test that is was called with the expected arguments (before functionUT completes), as well as unit test backgroundJob on its own.
If a promise exists but cannot be reached in a place that relies on its settlement, this is a potential design problem. A module that does asynchronous side effects on imports is another problem. Both concerns affect testability, also they can affect the application if the way it works changes.
Considering there's a promise, you have an option to chain or not chain it in a specific place. This doesn't mean it should be thrown away. In this specific case it can be possibly returned from a function that doesn't chain it.
A common way to do this is to preserve a promise at every point in case it's needed later, at least for testing purposes, but probably for clean shutdown, extending, etc.
const functionUT = async (deps) => {
await sleep(30) // Simulate func work
return {
status: 'done',
backgroundJob: backgroundJob(deps.externalService)...
};
}
const initialization = functionUT( appDeps )...
module.exports = {
functionUT,
initialization
}
In this form it's supposed to be tested like:
beforeAll(async () => {
let result = await initialization;
await result.backgroundJob;
});
...
let result = await functionUT(fakeDeps);
expect(result.status).toBe('done')
await result.backgroundJob;
expect(externaServiceMock).toBeCalledTimes(1);
Not waiting for initialization can result in open handler if test suite is short enough and cause a reasonable warning from Jest.
The test can be made faster by using Jest fake timers in right places together with flush-promises.
functionUT( appDeps ) call can be extracted from the module to cause a side effect only in the place where it's needed, e.g. in entry point. This way it won't interfere with the rest of tests that use this module. Also at least some functions can be extracted to their own modules to be mockable and improve testability (backgroundJob, as another answer suggests) because they cannot be mocked separately when they are declared in the same module the way they are.

java script promises lingo

I'm new to promises, so apologize for the newbie question. Inside my function, I have the following lines (middle of the function):
...
const retPromise = await buildImgs(file, imgArray);
retPromise.then(async function () {
console.log("completed build imgs");
...
My assumption was that the "then" from the promise would not execute until the await was completed. but alas, it is acting like sync code, and the retPromise evaluating the "then" before the buildImgs is completed (as measured by my console.log flows). The result is an undefined retPromise.
please help...what am I missing in the concept?
OK: after feedback, let me explaing further my question:
I am trying to understand this async/sync flow and control concept:
const retVal = somefunc();
console.log(retVal);
const retVal = await somefunc();
console.log(retVal);
in the first case, if I understand sync / async code correctly, then I should have a possibility that retVal is undefined when the console.log finds it...
in the second case, I thought it would stop flow until that point that somefunc() completes, then the flow would continue. However my reading seems to indicate it will still try to run the console.log message as a parallel thread. So this leads me to believe I would need to put the console.log inside of the .then after somefunc. Which leads me to promises. So I made a promise return, which I see happening.
However, the .then, as in my original post code, seems to post the console message "completed build imgs", before code inside my buildImgs completes (measured by time I know the function to take, and also console messages inside the buildImgs to help me with sequencing)
so it seems to me I am still missing a fundamental on how to block flow for async code. :(
When you use await construction the script waits until the promise resolves and return to your retPromise value from this promise.
So in this case better to choose one. Remove await and keep then, or keep await and use retPromise value.
Assuming that buildImgs is actually returning a promise (example)
const buildImgs = (file, imgArray) => {
return new Promise((resolve, reject) => {
try {
// ...
resolve()
} catch (err) {
reject(err)
}
})
}
When you call await on a promise its already waiting for the promise to complete, if you remove the await on the call then you can use .then
there are two ways to write promise handlers, the older way looks like this
buildImgs(file, imgArray)
.then(() => {
console.log('im done')
})
.catch(err => {
console.error(err)
})
and the newer syntax
// you must declare "async" in order to use "await"
const asyncFunction = async () => {
try {
await buildImgs(file, imgArray)
console.log('im done')
} catch(err) {
console.error(err)
}
}
asyncFunction()
if you need the return value from the promise, then you would assign it to a variable
const ineedResponse = await buildImgs(file, imgArray)
console.log(ineedResponse)
// or
buildImgs(file, imgArray)
.then(ineedResponse => {
console.log(ineedResponse)
})
.catch(err => {
console.error(err)
})

NodeJS: Wait for Status Code of Post Request

Background:
I have a gateway that returns the status code 200 via a post request if it is completely booted. If not it returns 500 while booting.
My idea:
I have a NodeJS application which should wait until the gateway returns 200. So I created a while loop which checks the state of the gateway.
My problem:
Unfortunately nothing works, the state is always true. Non of the log statements in the request will be display.
Do you have tips for me how I can fix this?
while (isGatewayUnavailable()) {
log.info('waiting for gateway ...');
sleep(60)
}
function isGatwayUnavailable() {
const url = '...'
let state = true
request.post(url, (err, res, body) => {
log.debug(0)
if (err) {
log.debug("Gateway offline");
log.debug("err: " + err);
}
else if (res.statusCode === 200) {
log.debug("Gateway online");
state = false;
cb(true);
}
else {
log.debug("Status Code: " + res.statusCode);
}
});
log.debug('return state: ' + state);
return state;
}
There is no "waiting" in JS. There's only "running code" and "running code in response to signals" (events, callbacks, promises). In this case, you want to do something based on a process that you do not control the timing of, so you can't use a synchronous function: by the time the function reaches its return keyword, you don't have any information to return yet..
So, instead of making your function return a value and having the caller wait for that value, make your code "do things once the information is in". That is, make your function either generate an event that you have a handler registered for, or pass a callback as argument so that your function can run that callback once it has the information necessary, or have it return a promise whose resolve (or reject) gets called once you have the information necessary.
1. Event-based:
const pubsub = ...;
function checkGatwayAvailability() {
request.post(url, (err, res, body) => {
pubsub.signal("gateway:availability", { available: ..., error: ... });
});
}
with caller code:
const pubsub = ...;
pubsub.register("gateway:availability", data => {...});
...
checkGatewayAvailability();
In this, the code that calls this and the code that handles the result are 100% detached from each other. Also note that pubsub isn't a real thing. Depending on your framework and APIs, there will be different ways to achieve event generation/handling, or you might even need to write your own (which really means "hit up npm and find one that is well documented and used by many folks, then use that").
2. Using a callback:
function checkGatwayAvailability(reportResult) {
request.post(url, (err, res, body) => {
reportResult({ available: ..., error: ... });
});
}
with caller code:
checkGatwayAvailability( result => {
...
});
In this approach, the calling and handling code are coupled in the sense that your call points to the handler, even if your handler is declared somewhere completely different, like:
checkGatwayAvailability(NetworkMonitor.handleGatewayResponse);
3. Using a promise:
function checkGatwayAvailability(reportResult) {
return new Promise((resolve, reject) => {
request.post(url, (err, res, body) => {
if (err) reject(err);
resolve(...);
});
});
}
with caller code:
checkGatwayAvailability().then(result => {...}).catch(err => {...});
Similar to a callback, the calling and handling code are coupled, but with promises you don't "guess" at whether the resultant information is good or bad, you literally have separate code paths for the "good" cases (handled by then), and the "bad" cases (handled by catch).
3b. Using a promise through async/await syntax:
In this case, request.post does not return a Promise, your function would still need to bake its own promise, so using an async declaration doesn't make a lot of sense. We can still use the await keyword in the calling code, though:
try {
const result = await checkGatwayAvailability();
} catch (e) {
...
}
but only if that caller code itself runs inside an async context.

How can I catch asynchronous-non-promised errors ? ( react to that specific error)

I know that there are answers out there but I didn't find a specific answer to my actual question.
Currently I use the following pattern a lot :
class A
{
getLocation()
{
return Promise.reject(2222222)
}
async a()
{
try
{
var loc = await this.getLocation();
alert(loc)
}
catch (e)
{
alert("catch 2")
}
}
}
new A().a();
Result : "catch 2"
Event If I throw an error in getLocation :
getLocation()
{
throw Error("ffffff")
}
- I get the same result - which is OK.
So where is the problem ?
Well as you know , an error which is thrown asynchronously-non-promised is a different beast :
So this code won't be catched at all:
getLocation() //bad code from a third party code , third party code
{
return new Promise((v, x) => setTimeout(() =>
{
throw Error("ffffff")
}, 100))
}
Question :
Regarding the fact that I want to catch errors - is there a better pattern for capturing this ?
Sure I can do :
window.onerror = function () { alert(4)}
But that would be not in order as the flow of .catch(...) or catch(){} , and I won't be able to do actions regarding that specific action that caused error.
Full disclosure:
No real life scenario. Learning purpose .
an error which is thrown asynchronously-non-promised is a different beast
Yes. And it must be avoided at all costs. Therefore, never put business code (including trivial things like property accesses) in asynchronous non-promise callbacks. It could throw! It should be obvious that JSON.parse can fail, that a property access can throw when the "object" is null or undefined or a getter is involved, or that a loop can fail when the thing that was supposed to be an array has no .length.
The only things that are allowed as asynchronous non-promise callbacks are resolve, reject, and (err, res) => { if (err) reject(err); else resolve(res); } (and maybe a variadic version for weird APIs with multiple arguments).
So rewrite the bad code to
async getLocation() {
await new Promise(resolve => setTimeout(resolve, 100));
throw Error("ffffff");
}
or
getLocation() {
return new Promise(resolve => setTimeout(resolve, 100)).then(res => {
throw Error("ffffff");
});
}
and when it's third-party code make them fix it, make an upstream merge request of your fix, or if those don't work abandon that party.
is there a better pattern for capturing this?
Well, domains (in node) were supposed to solve this problem of non-locality of asynchronous (uncaught) exceptions, but they didn't work out. Maybe some day, zones with better native language support will replace them.
The errors should be caught in place where they occur.
This kind of code code is incorrect and should be fixed in-place:
getLocation() //bad code from a third party code
{
return new Promise((v, x) => setTimeout(() =>
{
throw Error("ffffff")
}, 100))
}
If this is third-party code, it can be forked or patched.
Exceptions can be tracked globally by onerror, as the question already mentions. This should be used only to notify a developer of existing errors, not to handle them in normal way.
unhandledrejection event can be used for same purpose to notify about unhandled rejections in promises. It won't be able to handle the error in the snipped above because it is thrown inside setTimeout callback and doesn't result in promise rejection.
I guess the basic usage would be like this:
class A {
getLocation(x) {
return new Promise((resolve, reject) => setTimeout(() => {
// a lot of async code
try {
//simulate unexpected exception
if (x === 2) {
throw ("code error");
}
if (x) {
resolve('success');
} else {
reject('conditional rejection');
}
} catch (ex) {
reject(ex);
}
}, 1000));
}
async a(x) {
await this.getLocation(x).then((loc) => console.info(loc)).catch((e) => console.error(e));
}
}
let o = new A();
o.a(2);
o.a(0);
o.a(1);
The rejection of the Promise is not necessarily an code Exception as well as the code Exception should not necessarily trigger a Promise rejection.

How to test called promises not returned in tested function

First of all, I've been trying almost everything to make this code work, and I achieved, the problem is that I don't like the approach, and I wanted to know if there was something better I could do in order to make the TEST code more readable, but functional.
I want to assert (with sinon for example) that the second function (secondApiCall) has been called, but it seems like there is no way to make that happen, how would you make it happen. Is it there a non hacky approach?
The main problem here is that "I can't modify the functionToTest" and I have to write tests that would be basically checking that the API calls are being done.
How with the given code would you run the assertions after functionToTest has finished?
PS: The code is shit, I know, but sometimes you just have to deal with it, you can't do more thank just test the shit out of it before refactoring it :(
const firstApiCall = () => {
return new Promise(function(resolve,reject) {
setTimeout(() => {
resolve('firstApiCall success');
}, 3);
});
};
const secondApiCall = () => {
return new Promise(function(resolve,reject) {
setTimeout(() => {
resolve('secondApiCall success');
}, 3);
});
};
const functionToTest = () => {
setTimeout(() => {
firstApiCall().then(result => {
setTimeout(() => {
secondApiCall()
}, 2)
})
}, 15)
};
Basically the code that generates the mocks does something like this, so at the end you have synchronous code
const firstApiCall = () => {
return {
then: (cb) => {
cb('firstApiCall success')
}
}
};
Thank you very much!
You cannot check that the second API call did happen directly, unless the Promise is returned.
As described here:
const functionToTest = () => {
setTimeout(() => {
firstApiCall().then(result => {
setTimeout(() => {
secondApiCall()
}, 2)
})
}, 15)
};
the Promise is not returned so there's nothing you can do about it IMHO.
Mocking the function is not useful here I'd say.
If you cannot test it directly, you can try to test it indirectly: even if it's pretty bad, you could check via the API the function is calling, that the state of the resource behind the API has changed as expected after the second call.
If you cannot test via the API well... there's no much to test then.
After trying a couple of things, what you can always do, is mock the first API call, so at the end instead of an async call you would have a sync one thanks to callbacks, I will leave here an example, for whoever needs inspiration when testing promises that are not returned.
const firstApiCall = () => {
return {
then: (callback) => {
callback('firstApiCall success');
}
}
};
PS: Once again, this solution is not the prettiest but it works at least.

Categories