I am hard time writing test to assert something happened inside catch block which is executed inside forEach loop.
Prod code
function doSomething(givenResourceMap) {
givenResourceMap.forEach(async (resourceUrl) => {
try {
await axios.delete(resourceUrl);
} catch (error) {
logger.error(`Error on deleting resource ${resourceUrl}`);
logger.error(error);
throw error;
}
});
I am wanting to assert logger.error is being called twice and called with right arguments each time. So I wrote some test like this
describe('Do Something', () => {
it('should log message if fail to delete the resource', function() {
const resource1Url = chance.url();
const givenResourceMap = new Map();
const thrownError = new Error('Failed to delete');
givenResourceMap.set(resource1Url);
sinon.stub(logger, 'error');
sinon.stub(axios, 'delete').withArgs(resource1Url).rejects(thrownError);
await doSomething(givenResourceMap);
expect(logger.error).to.have.callCount(2);
expect(logger.error.getCall(0).args[0]).to.equal(`Error deleting resource ${resource1Url}`);
expect(logger.error.getCall(1).args[0]).to.equal(thrownError);
// Also need to know how to assert about `throw error;` line
});
});
I am using Mocha, sinon-chai, expect tests. Above test is failing saying logger.error is being 0 times.
Thanks.
The problem is that you are using await on a function that doesn't return a Promise. Note that doSomething is not async and does not return a Promise object.
The forEach function is async but that means they'll return right away with an unresolved Promise and you don't ever await on them.
In reality, doSomething will return before the work inside of the forEach is complete, which is probably not what you intended. To do that you could use a regular for-loop like this:
async function doSomething(givenResourceMap) {
for (const resourceUrl of givenResourceMap) {
try {
await axios.delete(resourceUrl);
} catch (error) {
logger.error(`Error on deleting resource ${resourceUrl}`);
logger.error(error);
throw error;
}
}
}
Note that it changes the return type of doSomething to be a Promise object rather than just returning undefined as it originally did. But it does let you do an await on it as you want to in the test (and presumably in production code also).
However since you re-throw the exception caught in the loop, your test will exit abnormally. The test code would have to also change to catch the expected error:
it('should log message if fail to delete the resource', function(done) {
// ... the setup stuff you had before...
await doSomething(givenResourceMap).catch(err => {
expect(logger.error).to.have.callCount(2);
expect(logger.error.getCall(0).args[0]).to.equal(`Error deleting resource ${resource1Url}`);
expect(logger.error.getCall(1).args[0]).to.equal(thrownError);
done();
});
});
Related
This is a design question that came up to me while unit testing.
Let's dive into the example:
Imagine this:
async function foo() {
try {
return apiCall()
}
catch (e) {
throw new CustomError(e);
}
}
async function bar() {
return foo()
}
async function main() {
try {
await bar()
}catch(e) {
console.error(e)
}
}
main()
What do we see here? That the only function that hasn't got a try-catch block is bar.
But if foo fails, it should get catched by the main catch.
While unittesting this like
describe('testing bar', () => {
it('foo should throw', () => {
foo.mockImplementantion(() => { throw new CustomError('error')});
bar()
.then((result) => console.log(result))
.catch((err) => { exepect(err).toBeInstanceOf(CustomError)}) // this is what we are testing
})
})
The output we see is that an Unhandled promise rejection is logged in the console.
So, my question is... even if I know that the main() will catch the error, should I use try-catch block inside all async functions?
try..catch may be necessary if a function is able to recover from an error, do a side effect like logging, or re-throw a more meaningful error.
If CustomError is more preferable than an error that apiCall can throw then try..catch necessary, otherwise it doesn't. Also the problem with foo is that it handles only synchronous errors. In order to handle rejected promises, it should be return await apiCall(), this is a known pitfall of async.
Uncaught rejections are unwanted, they currently result in UnhandledPromiseRejectionWarning and are expected to crash Node in future versions. It's preferable to handle an error in a meaningful way at top level, so main needs to catch the error. This can be delegated to process uncaughtRejection event handler but it may be beneficial for it to stay extra level of error handling that should be never reached.
The output we see is that an Unhandled promise rejection is logged in the console.
This shouldn't happen. A rejection needs to be handled by the test. One possible point of failure is explained above, foo can return original error from apiCall instead of CustomError in case it wasn't correctly mocked, this will fail the expectation and result in unhandled rejection in catch(). Another point of failure is that the test has unchained promise because it wasn't returned, the test always passes.
Asynchronous test that uses promises should always return a promise. This can be improved by using async..await. foo is async, it's expected to always return a promise:
it('foo should throw', async () => {
foo.mockImplementantion(() => { return Promise.reject(new CustomError('error')) });
await expect(bar()).rejects.toThrow(CustomError);
})
Now even if foo mock fails (foo mock won't affect bar if they are defined in the same module as shown) and bar rejects with something that is not CustomError, this will be asserted.
No. You don't need to use try/catch in every async/await. You only need to do it at the top level. In this case your main function which you are already doing.
Weather you should is a matter of opinion. The go language designers feel strongly enough about this that is has become the standard in go to always handle errors at each function call. But this is not the norm in javascript or most other languages.
Unhandled promise rejection
Your unhandled promise rejection is thrown by your it() function because you are not telling it to wait for the promise to complete.
I assume you are using something like mocha for the unit test (other frameworks may work differently). In mocha there are two ways to handle asynchronous tests:
Call the done callback - the it() function will always be called with a done callback. It is up to you weather you want to use it or like in your posted code to not use it:
describe('testing bar', () => {
it('foo should throw', (done) => {
foo.mockImplementantion(() => { throw new CustomError('error')});
bar()
.then((result) => {
console.log(result);
done(); // ------------- THIS IS YOUR ACTUAL BUG
})
.catch((err) => {
exepect(err).toBeInstanceOf(CustomError);
done(); // ------------- THIS IS YOUR ACTUAL BUG
})
})
})
Return a Promise. If you return a promise to the it() function mocha will be aware that your code is asynchronous and wait for completion:
describe('testing bar', () => {
it('foo should throw', (done) => {
foo.mockImplementantion(() => { throw new CustomError('error')});
return bar() // <----------- THIS WOULD ALSO FIX IT
.then((result) => {
console.log(result);
})
.catch((err) => {
exepect(err).toBeInstanceOf(CustomError);
})
})
})
In short, there is nothing wrong with your code. But you have a bug in your unit test.
As #Bergi told me I will post some solutions right here
I wrap the function in a try catch block
1.
async function bar() {
try{
return foo()
} catch (e) {
throw e
}
}
Rewrite the test
describe('testing bar', () => {
it('foo should throw', (done) => {
foo.mockImplementantion(() => { throw new CustomError('error')});
bar()
.then((result) => { throw result }) // this is because we are expecting an error, so if the promise resolves it's actually a bad sign.
.catch((err) => {
exepect(err).toBeInstanceOf(CustomError)}) // this is what we are testing
done();
})
})
Use return in the test case
describe('testing bar', () => {
it('foo should throw', () => {
foo.mockImplementantion(() => { throw new CustomError('error')});
return bar()
.then((result) => { throw result })
.catch((err) => { exepect(err).toBeInstanceOf(CustomError)}) // this is what we are testing
})
})
I have the following code in my own async function that uses another imported function from module which is a custom wrap of axios inside try/catch block:
async function getCharacter (realmSlug, characterName) {
try {
const [{id, name, gender, faction, race, character_class, active_spec, realm, guild, level, last_login_timestamp, average_item_level, equipped_item_level}, {pets, unlocked_battle_pet_slots},{mounts}] = await Promise.all([
getCharacterSummary(realmSlug, characterName), -- custom axios instance
getCharacterPetsCollection(realmSlug, characterName),
getCharacterMountsCollection(realmSlug, characterName)
])
....
return result;
} catch (error) {
console.log(error.code);
if (error.response.status === 404 || error.response.status === 403) {
console.error(`${getCharacter.name},${characterName}#${realmSlug}`);
}
return { name: characterName, realm: realmSlug }
}
}
The problem is that if I use promise.all according to Stackoverflow 1,2 I can not handle errors. So the problem is when I call function to execute, my errors doesn't handle in (catch) block. At all. Even if I don't need print them, anyway I receive messages in console about 404 errors, but console.log(error.code) still gives me nothing. For example:
So is there any way to handle this annoying error messages in console somehow?
For example using .catch somewhere? Or using for await ... of or rxJS instead if it's possible?
Exporting function and using .catch
Even if I export this function getCharacter in another .js file and use the following code:
const getCharacter = require('./getCharacter');
let bulkCharacters = [{realmSlug, characterName},{realmSlug, characterName},... ,n] //array of characters for getCharacter request
const promises = bulkCharacters.map(async ({realmSlug, characterName}) => {
try {
return await getCharacter(realmSlug, characterName);
} catch (e) {
console.log(e)
}
});
let test = await Promise.all(promises)
.catch(function(arrayOfPromises, err) {
// log that I have an error, return the entire array;
console.log('A promise failed to resolve', err);
return arrayOfPromises;
})
.then(function(arrayOfPromises) {
console.log(arrayOfPromises)
})
;
console.log('stop')
I still receive errors in console, without triggering catch block inside getCharacter function or this file in which this function was imported and catch block is outside the function.
I like the flatness of the new Async/Await feature available in Typescript, etc. However, I'm not sure I like the fact that I have to declare the variable I'm awaiting on the outside of a try...catch block in order to use it later. Like so:
let createdUser
try {
createdUser = await this.User.create(userInfo)
} catch (error) {
console.error(error)
}
console.log(createdUser)
// business
// logic
// goes
// here
Please correct me if I'm wrong, but it seems to be best practice not to place multiple lines of business logic in the try body, so I'm left only with the alternative of declaring createdUser outside the block, assigning it in the block, and then using it after.
What is best practice in this instance?
It seems to be best practice not to place multiple lines of business logic in the try body
Actually I'd say it is. You usually want to catch all exceptions from working with the value:
try {
const createdUser = await this.User.create(userInfo);
console.log(createdUser)
// business logic goes here
} catch (error) {
console.error(error) // from creation or business logic
}
If you want to catch and handle errors only from the promise, you have three choices:
Declare the variable outside, and branch depending on whether there was an exception or not. That can take various forms, like
assign a default value to the variable in the catch block
return early or re-throw an exception from the catch block
set a flag whether the catch block caught an exception, and test for it in an if condition
test for the value of the variable to have been assigned
let createdUser; // or use `var` inside the block
try {
createdUser = await this.User.create(userInfo);
} catch (error) {
console.error(error) // from creation
}
if (createdUser) { // user was successfully created
console.log(createdUser)
// business logic goes here
}
Test the caught exception for its type, and handle or rethrow it based on that.
try {
const createdUser = await this.User.create(userInfo);
// user was successfully created
console.log(createdUser)
// business logic goes here
} catch (error) {
if (error instanceof CreationError) {
console.error(error) // from creation
} else {
throw error;
}
}
Unfortunately, standard JavaScript (still) doesn't have syntax support for conditional exceptions.
If your method doesn't return promises that are rejected with specific enough errors, you can do that yourself by re-throwing something more appropriate in a .catch() handler:
try {
const createdUser = await this.User.create(userInfo).catch(err => {
throw new CreationError(err.message, {code: "USER_CREATE"});
});
…
} …
See also Handling multiple catches in promise chain for the pre-async/await version of this.
Use then with two callbacks instead of try/catch. This really is the least ugly way and my personal recommendation also for its simplicity and correctness, not relying on tagged errors or looks of the result value to distinguish between fulfillment and rejection of the promise:
await this.User.create(userInfo).then(createdUser => {
// user was successfully created
console.log(createdUser)
// business logic goes here
}, error => {
console.error(error) // from creation
});
Of course it comes with the drawback of introducing callback functions, meaning you cannot as easily break/continue loops or do early returns from the outer function.
Another simpler approach is to append .catch to the promise function. ex:
const createdUser = await this.User.create(userInfo).catch( error => {
// handle error
})
Cleaner code
using async/await with Promise catch handler.
From what I see, this has been a long-standing problem that has bugged (both meanings) many programmers and their code. The Promise .catch is really no different from try/catch.
Working harmoniously with await/async, ES6 Promise's catch handler provides a proper solution and make code cleaner:
const createUser = await this.User
.create(userInfo)
.catch(error => console.error(error))
console.log(createdUser)
// business
// logic
// goes
// here
Note that while this answers the question, it gobbles up the error. The intention must be for the execution to continue and not throw. In this case, it's usually always better to be explicit and return false from catch and check for user:
.catch(error => {
console.error(error);
return false
})
if (!createdUser) // stop operation
In this case, it is better to throw because (1) this operation (creating a user) is not expected to failed, and (2) you are likely not able to continue:
const createUser = await this.User
.create(userInfo)
.catch(error => {
// do what you need with the error
console.error(error)
// maybe send to Datadog or Sentry
// don't gobble up the error
throw error
})
console.log(createdUser)
// business
// logic
// goes
// here
Learning catch doesn't seem like worth it?
The cleanliness benefits may not be apparent above, but it adds up in real-world complex async operations.
As an illustration, besides creating user (this.User.create), we can push notification (this.pushNotification) and send email (this.sendEmail).
this.User.create
this.User.create = async(userInfo) => {
// collect some fb data and do some background check in parallel
const facebookDetails = await retrieveFacebookAsync(userInfo.email)
.catch(error => {
// we can do some special error handling
// and throw back the error
})
const backgroundCheck = await backgroundCheckAsync(userInfo.passportID)
if (backgroundCheck.pass !== true) throw Error('Background check failed')
// now we can insert everything
const createdUser = await Database.insert({ ...userInfo, ...facebookDetails })
return createdUser
}
this.pushNotifcation and this.sendEmail
this.pushNotification = async(userInfo) => {
const pushed = await PushNotificationProvider.send(userInfo)
return pushed
})
this.sendEmail = async(userInfo) => {
const sent = await mail({ to: userInfo.email, message: 'Welcome' })
return sent
})
Compose the operations:
const createdUser = await this.User
.create(userInfo)
.catch(error => {
// handle error
})
// business logic here
return await Promise.all([
this.pushNotification(userInfo),
this.sendEmail(userInfo)
]).catch(error => {
// handle errors caused
// by pushNotification or sendEmail
})
No try/catch. And it's clear what errors you are handling.
I usually use the Promise's catch() function to return an object with an error property on failure.
For example, in your case i'd do:
const createdUser = await this.User.create(userInfo)
.catch(error => { error }); // <--- the added catch
if (Object(createdUser).error) {
console.error(error)
}
If you don't like to keep adding the catch() calls, you can add a helper function to the Function's prototype:
Function.prototype.withCatcher = function withCatcher() {
const result = this.apply(this, arguments);
if (!Object(result).catch) {
throw `${this.name}() must return a Promise when using withCatcher()`;
}
return result.catch(error => ({ error }));
};
And now you'll be able to do:
const createdUser = await this.User.create.withCatcher(userInfo);
if (Object(createdUser).error) {
console.error(createdUser.error);
}
EDIT 03/2020
You can also add a default "catch to an error object" function to the Promise object like so:
Promise.prototype.catchToObj = function catchToObj() {
return this.catch(error => ({ error }));
};
And then use it as follows:
const createdUser = await this.User.create(userInfo).catchToObj();
if (createdUser && createdUser.error) {
console.error(createdUser.error);
}
#Bergi Answer is good, but I think it's not the best way because you have to go back to the old then() method, so i think a better way is to catch the error in the async function
async function someAsyncFunction(){
const createdUser = await this.User.create(userInfo);
console.log(createdUser)
}
someAsyncFunction().catch(console.log);
But what if we have many await in the same function and need to catch every error?
You may declare the to() function
function to(promise) {
return promise.then(data => {
return [null, data];
})
.catch(err => [err]);
}
And then
async function someAsyncFunction(){
let err, createdUser, anotherUser;
[err, createdUser] = await to(this.User.create(userInfo));
if (err) console.log(`Error is ${err}`);
else console.log(`createdUser is ${createdUser}`);
[err, anotherUser] = await to(this.User.create(anotherUserInfo));
if (err) console.log(`Error is ${err}`);
else console.log(`anotherUser is ${anotherUser}`);
}
someAsyncFunction();
When reading this its: "Wait to this.User.create".
Finally you can create the module "to.js" or simply use the await-to-js module.
You can get more information about to function in this post
await this.User.create(userInfo).then(async data => await this.emailService.sendEmail(data.email), async error => await this.sentryService.sendReport(error))
async function(req, res) {
try {
const user = await userCtrl.getUser();
const userMaps = await mapsCtrl.findDetails(user.mapId);
res.send(userMaps);
} catch (error) {
//handle error
res.status(400).send(error)
}
}
// user controll
function getUser() {
return new Promise(function(resolve, reject) {
//data base read using mysql
req.app.get("mysqlConn").query(query, function(error, results, fields) {
if (error) {
reject(error);
}
resolve(results);
});
})
}
//maps controller function is also like above one.
This is the code handle part of an express get route. Sometimes the rejected code is not getting caught. I get the error returned from MySQL in 200 status code.
Yes you can write multiple awaits in a single try catch block. so your catch block will receive the error if any of the above await fails.
Refer this link for more information about async-await - https://javascript.info/async-await
reject() or resolve () doesn't mean the functions is terminated. So we have to explicitly return from the function to avoid further code execution. In your case put resolve in else block or just put return statement in the if block after the reject is called!
Refer this link for more information:- Do I need to return after early resolve/reject?
I hope this help :)
Regards.
I'm writing an async test that expects the async function to throw like this:
it("expects to have failed", async () => {
let getBadResults = async () => {
await failingAsyncTest()
}
expect(await getBadResults()).toThrow()
})
But jest is just failing instead of passing the test:
FAIL src/failing-test.spec.js
● expects to have failed
Failed: I should fail!
If I rewrite the test to looks like this:
expect(async () => {
await failingAsyncTest()
}).toThrow()
I get this error instead of a passing test:
expect(function).toThrow(undefined)
Expected the function to throw an error.
But it didn't throw anything.
You can test your async function like this:
it('should test async errors', async () => {
await expect(failingAsyncTest())
.rejects
.toThrow('I should fail');
});
'I should fail' string will match any part of the error thrown.
I'd like to just add on to this and say that the function you're testing must throw an actual Error object throw new Error(...). Jest does not seem to recognize if you just throw an expression like throw 'An error occurred!'.
await expect(async () => {
await someAsyncFunction(someParams);
}).rejects.toThrowError("Some error message");
We must wrap the code in a function to catch the error. Here we are expecting the Error message thrown from someAsyncFunction should be equal to "Some error message". We can call the exception handler also
await expect(async () => {
await someAsyncFunction(someParams);
}).rejects.toThrowError(new InvalidArgumentError("Some error message"));
Read more https://jestjs.io/docs/expect#tothrowerror
Custom Error Class
The use of rejects.toThrow will not work for you. Instead, you can combine the rejects method with the toBeInstanceOf matcher to match the custom error that has been thrown.
Example
it("should test async errors", async () => {
await expect(asyncFunctionWithCustomError()).rejects.toBeInstanceOf(
CustomError
)
})
To be able to make many tests conditions without having to resolve the promise every time, this will also work:
it('throws an error when it is not possible to create an user', async () => {
const throwingFunction = () => createUser(createUserPayload)
// This is what prevents the test to succeed when the promise is resolved and not rejected
expect.assertions(3)
await throwingFunction().catch(error => {
expect(error).toBeInstanceOf(Error)
expect(error.message).toMatch(new RegExp('Could not create user'))
expect(error).toMatchObject({
details: new RegExp('Invalid payload provided'),
})
})
})
I've been testing for Firebase cloud functions and this is what I came up with:
test("It should test async on failing cloud functions calls", async () => {
await expect(async ()=> {
await failingCloudFunction(params)
})
.rejects
.toThrow("Invalid type"); // This is the value for my specific error
});
This is built on top of lisandro's answer.
If you want to test that an async function does NOT throw:
it('async function does not throw', async () => {
await expect(hopefullyDoesntThrow()).resolves.not.toThrow();
});
The above test will pass regardless of the value returned, even if undefined.
Keep in mind that if an async function throws an Error, its really coming back as a Promise Rejection in Node, not an error (thats why if you don't have try/catch blocks you will get an UnhandledPromiseRejectionWarning, slightly different than an error). So, like others have said, that is why you use either:
.rejects and .resolves methods, or a
try/catch block within your tests.
Reference:
https://jestjs.io/docs/asynchronous#asyncawait
This worked for me
it("expects to have failed", async () => {
let getBadResults = async () => {
await failingAsyncTest()
}
expect(getBadResults()).reject.toMatch('foo')
// or in my case
expect(getBadResults()).reject.toMatchObject({ message: 'foo' })
})
You can do like below if you want to use the try/catch method inside the test case.
test("some test case name with success", async () => {
let response = null;
let failure = null;
// Before calling the method, make sure someAsyncFunction should be succeeded
try {
response = await someAsyncFunction();
} catch(err) {
error = err;
}
expect(response).toEqual(SOME_MOCK_RESPONSE)
expect(error).toBeNull();
})
test("some test case name with failure", async () => {
let response = null;
let error = null;
// Before calling the method, make sure someAsyncFunction should throw some error by mocking in proper way
try {
response = await someAsyncFunction();
} catch(err) {
error = err;
}
expect(response).toBeNull();
expect(error).toEqual(YOUR_MOCK_ERROR)
})
Edit:
As my given solution is not taking the advantage of inbuilt jest tests with the throwing feature, please do follow the other solution suggested by #Lisandro https://stackoverflow.com/a/47887098/8988448
it('should test async errors', async () => {
await expect(failingAsyncTest())
.rejects
.toThrow('I should fail');
});
test("It should test async on failing cloud functions calls", async () => {
failingCloudFunction(params).catch(e => {
expect(e.message).toBe('Invalid type')
})
});