Handle promise rejection with Chai - javascript

I have the following validation function checking if a user exists:
validate.user = async (user, password) => {
const matches = await bcrypt.compare(password, user.password);
if (matches) return user;
if (!matches) {
return validate.logAndThrow('User password does not match');
}
return validate.logAndThrow('Error in bcrypt compare');
};
The following test using Chai should test this:
chai.use(sinonChai);
const { expect } = chai;
describe('#user', () => {
it('show throw if user is undefined', () => {
expect(() => validate.user(undefined, 'pass')).to.throw('User does not exist');
});
The error does get thrown, however mocha shows the following in the console:
2) show throw if user is undefined
(node:18587) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: User does not exist
So my question is, how I can rewrite the test such that it does catch promise rejections?

For testing promise-based functions (async) use chai-as-promised plugin:
expect(validate.user(undefined, 'pass')).to.be.rejectedWith(Error, 'User does not exist');
In this case you do not put () => validate.user() into expect because it will not return a promise. async functions always return a promise, so it's enough to just call your method inside expect (without wrapping it into a method).
I hope this is clear :) If you have any questions - feel free to ask.

Im not totally sure it is mocha that throws that unhandled rejection.
I think it is your bcrypt.compare which is async by nature and uses callbacks. await can't really be used out of the box with async functions.
but you can promisify the async calls with nodes util library like this:
const util = require('util');
const bcrypt = require('bcrypt');
const bcryptCompare = util.promisify(bcrypt.compare);
Now you can use await bcryptCompare(password, user.password); like you intended. please note that i changed the constant after the await call.
Also to handle rejections with async/await you should use try/catch like this:
let matches;
try {
matches = await bcryptCompare(password, user.password);
} catch(bcryptError) {
throw new Error(bcryptError);
}

Related

Write jest unit testcases for exception handling getting issue on while run

This is my controller class:
#Put()
async updateUser(#BodyToClass() user: UpsertUserDto): Promise<UpsertUserDto> {
try {
return await this.userService.updateUser(user);
} catch (e) {
throw new HttpException(e.message, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
This is my Usercontroller.spec test class, I am writing the testcases for exception handling for negative cases.
While running getting error on this line:
".then(() => done.fail('Client controller should return INTERNAL_SERVER_ERROR 500 '))
Here is my failure message from Jest:
at Env.fail (../node_modules/jest-jasmine2/build/jasmine/Env.js:722:61) at user/user.controller.spec.ts:51:33 at Object. (user/user.controller.spec.ts:50:15)
I am not sure where I am doing mistake or whether there is other way to handle the exception in nestjs.
it('should throw INTERNAL_SERVER_ERROR if user not update', async (done) => {
const expectedResult = undefined;
const testuser = new UpsertUserDto();
testuser.id = '123';
jest.spyOn(userservice, 'updateUser').mockResolvedValue(expectedResult);
await usercontroller.updateUser(testuser)
.then(() => done.fail('Client controller should return INTERNAL_SERVER_ERROR 500 '))
.catch((error) => {
expect(error.status).toBe(500);
expect(error.message).toMatchObject({error: ' INTERNAL_SERVER_ERROR', statusCode: 500});
done();
});
});
You're using mockResolvedValue in your jest.spyOn which means that in a .then chain, the .catch will never be executed. Looking at your code, all that's happening it your UserService method returns undefined and as there is no logic around it, the controller then returns that undefined meaning there's no error to be caught. If you are trying to test an error, you should be using mockRejectedValue instead. Also, it's usually not the best practice to mix using async/await with using a done callback, as it can lead to some weird situations.
And lastly, Jest has a built in way to check for errors from a function. Your test could look something like this instead:
it('should throw INTERNAL_SERVER_ERROR if user not update', async () => {
const testuser = new UpsertUserDto();
testuser.id = '123';
jest.spyOn(userservice, 'updateUser').mockRejectedValue(new Error('There was an error'));
await expect(usercontroller.updateUser(testuser)).rejects.toThrow(HttpException);
await expect(usercontroller.updateUser(testuser)).rejects.toThrow('There was an error');
});
I suggest you spend some time reading Jest's documentation and possibly looking at some examples to get your feet on the ground with testing.

Testing async/await method. Exception not being caught by Jest in unit test

I'm attempting to test some code that uses await and async using Jest. The problem I'm having is an exception is thrown, but Jest doesn't seem to catch it.
For example, here is run method that checks to see if session.url is present. If not, an exception is thrown:
const args = require('args');
const fs = require('fs');
const { promisify } = require('util');
const readFile = promisify(fs.readFile);
// Loads JSON configuration file
module.exports.loadConfigFile = async (filename) => {
const contents = await readFile(filename, 'utf8');
return JSON.parse(contents);
};
// Prepare session ensuring command line flags override config
module.exports.prepareSession = async (flags) => {
if(!flags.config) return flags;
const config = await this.loadConfigFile(flags.config);
return {...config, ...flags};
};
// Runs the race application
module.exports.run = async (flags = {}) => {
const session = await this.prepareSession(flags);
if(!session.url) throw new Error('A valid URL is required');
};
Here I test to see if an exception is thrown:
describe('Race Module', () => {
test('Throws exception if no URL is provided', async () => {
const runner = await race.run();
expect(runner).toThrowError();
});
...
The test runs and it appears an exception is thrown but jest hasn't caught it and it doesn't pass:
Race Module
✕ Throws exception if no URL is provided (3ms)
● Race Module › Throws exception if no URL is provided
A valid URL is required
at Object.<anonymous>.module.exports.run (src/race.js:23:27)
at <anonymous>
Any ideas where I'm going wrong?
My initial thought was to chain catch(() => {}) to race.run() in the test but I am not entirely sure how I would test that. That might not even be the problem.
The fix was to use rejects.toThrow. But, note that this functionality is currently broken in master. I had to use a beta branch. See here: https://github.com/facebook/jest/pull/4884
test('Throws exception if no URL is provided', async () => {
await expect(race.run())
.rejects
.toThrow('A valid URL is required');
});

Can you write async tests that expect toThrow?

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

How to make catched promises fail in jest

Consider this function:
aPromise = require('axios');
function middleware(callback) {
axios.get('/api/get')
.then(callback)
.catch(callback);
}
Consider this test:
const callback = (err) => {
expect(isError(err)).toBe(true);
done();
};
middleware(callback);
The isError is a lodash function.
Consider aPromise as something I want to test. If the promise always resolves, this test should not pass. But it will! And that's because the promise's catch actually catches the expect exception.
My question is: How to not catch the error in a promise's catch handler when expect throws an error in the promise's then handler?
Note that I don't use async/await.
You need to create a failed promise and you need to return the promise in your test. Please have a look on the doc on testing promises.
aPromise = require('axios');
jest.mock('axios', () => {
get: ()=> jest.fn() //initialy mock the get function
})
it('catch failing promises',() = > {
const result = Promise.reject('someError'); //create a rejected promises
aPromise.get.mockImplementation(() => result)// let `get` return the rejected promise
const callback = jest.fn()
middleware(callback)
return result
.then (()=>{
expect(callback).toHaveBeenCalledWith('someError');
})
})

Getting a UnhandledPromiseRejectionWarning when testing using mocha/chai

So, I'm testing a component that relies on an event-emitter. To do so I came up with a solution using Promises with Mocha+Chai:
it('should transition with the correct event', (done) => {
const cFSM = new CharacterFSM({}, emitter, transitions);
let timeout = null;
let resolved = false;
new Promise((resolve, reject) => {
emitter.once('action', resolve);
emitter.emit('done', {});
timeout = setTimeout(() => {
if (!resolved) {
reject('Timedout!');
}
clearTimeout(timeout);
}, 100);
}).then((state) => {
resolved = true;
assert(state.action === 'DONE', 'should change state');
done();
}).catch((error) => {
assert.isNotOk(error,'Promise error');
done();
});
});
On the console I'm getting an 'UnhandledPromiseRejectionWarning' even though the reject function is getting called since it instantly shows the message 'AssertionError: Promise error'
(node:25754) UnhandledPromiseRejectionWarning: Unhandled promise
rejection (rejection id: 2): AssertionError: Promise error: expected
{ Object (message, showDiff, ...) } to be falsy
should transition with the correct event
And then, after 2 sec I get
Error: timeout of 2000ms exceeded. Ensure the done() callback is
being called in this test.
Which is even weirder since the catch callback was executed(I think that for some reason the assert failure prevented the rest of the execution)
Now the funny thing, if I comment out the assert.isNotOk(error...) the test runs fine without any warning in the console. It stills 'fails' in the sense that it executes the catch.
But still, I can't understand these errors with promise. Can someone enlighten me?
The issue is caused by this:
.catch((error) => {
assert.isNotOk(error,'Promise error');
done();
});
If the assertion fails, it will throw an error. This error will cause done() never to get called, because the code errored out before it. That's what causes the timeout.
The "Unhandled promise rejection" is also caused by the failed assertion, because if an error is thrown in a catch() handler, and there isn't a subsequent catch() handler, the error will get swallowed (as explained in this article). The UnhandledPromiseRejectionWarning warning is alerting you to this fact.
In general, if you want to test promise-based code in Mocha, you should rely on the fact that Mocha itself can handle promises already. You shouldn't use done(), but instead, return a promise from your test. Mocha will then catch any errors itself.
Like this:
it('should transition with the correct event', () => {
...
return new Promise((resolve, reject) => {
...
}).then((state) => {
assert(state.action === 'DONE', 'should change state');
})
.catch((error) => {
assert.isNotOk(error,'Promise error');
});
});
For those who are looking for the error/warning UnhandledPromiseRejectionWarning outside of a testing environment, It could be probably because nobody in the code is taking care of the eventual error in a promise:
For instance, this code will show the warning reported in this question:
new Promise((resolve, reject) => {
return reject('Error reason!');
});
(node:XXXX) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Error reason!
and adding the .catch() or handling the error should solve the warning/error
new Promise((resolve, reject) => {
return reject('Error reason!');
}).catch(() => { /* do whatever you want here */ });
Or using the second parameter in the then function
new Promise((resolve, reject) => {
return reject('Error reason!');
}).then(null, () => { /* do whatever you want here */ });
I got this error when stubbing with sinon.
The fix is to use npm package sinon-as-promised when resolving or rejecting promises with stubs.
Instead of ...
sinon.stub(Database, 'connect').returns(Promise.reject( Error('oops') ))
Use ...
require('sinon-as-promised');
sinon.stub(Database, 'connect').rejects(Error('oops'));
There is also a resolves method (note the s on the end).
See http://clarkdave.net/2016/09/node-v6-6-and-asynchronously-handled-promise-rejections
The assertion libraries in Mocha work by throwing an error if the assertion was not correct. Throwing an error results in a rejected promise, even when thrown in the executor function provided to the catch method.
.catch((error) => {
assert.isNotOk(error,'Promise error');
done();
});
In the above code the error objected evaluates to true so the assertion library throws an error... which is never caught. As a result of the error the done method is never called. Mocha's done callback accepts these errors, so you can simply end all promise chains in Mocha with .then(done,done). This ensures that the done method is always called and the error would be reported the same way as when Mocha catches the assertion's error in synchronous code.
it('should transition with the correct event', (done) => {
const cFSM = new CharacterFSM({}, emitter, transitions);
let timeout = null;
let resolved = false;
new Promise((resolve, reject) => {
emitter.once('action', resolve);
emitter.emit('done', {});
timeout = setTimeout(() => {
if (!resolved) {
reject('Timedout!');
}
clearTimeout(timeout);
}, 100);
}).then(((state) => {
resolved = true;
assert(state.action === 'DONE', 'should change state');
})).then(done,done);
});
I give credit to this article for the idea of using .then(done,done) when testing promises in Mocha.
I faced this issue:
(node:1131004) UnhandledPromiseRejectionWarning: Unhandled promise rejection (re
jection id: 1): TypeError: res.json is not a function
(node:1131004) DeprecationWarning: Unhandled promise rejections are deprecated.
In the future, promise rejections that are not handled will terminate the Node.j
s process with a non-zero exit code.
It was my mistake, I was replacing res object in then(function(res), so changed res to result and now it is working.
Wrong
module.exports.update = function(req, res){
return Services.User.update(req.body)
.then(function(res){//issue was here, res overwrite
return res.json(res);
}, function(error){
return res.json({error:error.message});
}).catch(function () {
console.log("Promise Rejected");
});
Correction
module.exports.update = function(req, res){
return Services.User.update(req.body)
.then(function(result){//res replaced with result
return res.json(result);
}, function(error){
return res.json({error:error.message});
}).catch(function () {
console.log("Promise Rejected");
});
Service code:
function update(data){
var id = new require('mongodb').ObjectID(data._id);
userData = {
name:data.name,
email:data.email,
phone: data.phone
};
return collection.findAndModify(
{_id:id}, // query
[['_id','asc']], // sort order
{$set: userData}, // replacement
{ "new": true }
).then(function(doc) {
if(!doc)
throw new Error('Record not updated.');
return doc.value;
});
}
module.exports = {
update:update
}
Here's my take experience with E7 async/await:
In case you have an async helperFunction() called from your test... (one explicilty with the ES7 async keyword, I mean)
→ make sure, you call that as await helperFunction(whateverParams) (well, yeah, naturally, once you know...)
And for that to work (to avoid ‘await is a reserved word’), your test-function must have an outer async marker:
it('my test', async () => { ...
I had a similar experience with Chai-Webdriver for Selenium.
I added await to the assertion and it fixed the issue:
Example using Cucumberjs:
Then(/I see heading with the text of Tasks/, async function() {
await chai.expect('h1').dom.to.contain.text('Tasks');
});
Just a heads-up that you can get a UnhandledPromiseRejectionWarning if you accidentally put your test code outside of the it-function. 😬
describe('My Test', () => {
context('My Context', () => {
it('should test something', () => {})
const result = testSomething()
assert.isOk(result)
})
})

Categories