Catching thrown errors with SinonJS - node.js

I've got a method that may throw an Error, but I'm having trouble writing a SinonJS/Mocha/Should unit test case for this condition.
Sample function under test:
function testError(value) {
if (!value) {
throw new Error('No value');
return false;
}
};
Sample test:
describe('#testError', function() {
it('throws an error', function() {
var spy = sinon.spy(testError);
testError(false);
spy.threw().should.be.true();
});
});
This outputs:
#testError
1) throws an error
0 passing (11ms)
1 failing
1) #testError throws an error:
Error: No value
at testError (tests/unit/js/test-error.js:6:14)
at Context.<anonymous> (tests/unit/js/test-error.js:14:6)
I was expecting Sinon to catch the Error and allow me to spy on the throw, but it seems to fail the test instead. Any ideas?
I referred to Don't sinon.js spys catch errors? but the only solution there is to use expect. I'd prefer to keep with a single assertion library if possible.

It appears that this works inside a try/catch:
function foo() { throw new Error("hey!"); }
var fooSpy = sinon.spy(foo);
try {
fooSpy();
} catch (e) {
// pass
}
assert(fooSpy.threw());
Note that you have to call fooSpy, not foo itself.
But also note that .should.be.true() is not part of Sinon, so you're probably already using Chai or a similar library, in which case the expect(foo).to.have.thrown() or assert.throws(foo, someError) syntax seems much nicer.
Update: If you're using ShouldJS, looks like you can use should.throws. I still think this is nicer than using the Sinon version for this purpose.

Revised
Following #nrabinowitz's helpful advice, here's a solution that uses should.throws. This avoids using Sinon.spy altogether.
describe('#testError', function() {
it('throws an error', function() {
should.throws(function() {
testError(false);
});
});
});

const bootstrap = async anyParam => {
if(!anyParam){
throw new Error('test')
}
await anyParam()
}
const spy = sinon.spy(bootstrap)
try {
await spy();
} catch (e) {
expect(e.message).to.be.equal('test')
spy.called = false
}
expect(spy.called).to.be.equal(false);

Related

How to test catch block of a Typescript function code using mocha and chai

I have typescript class with some functions in it. Each function has a try catch block where upon hitting the catch it returns a pre defined response.
I am writing unit tests using mocha and chai and I am having trouble trying to explicitly hit the catch blocks.
For example, consider this simple function below
public async verifyCode(email: string, code: string) {
try {
let result: CodeInterface | null = //call db to get matching code
if(result === null) {
return {status: 401, message: INCORRECT_FELLOWSHIP_CODE_MESSAGE};
}
return result._id;
} catch (error) {
Logger.log(LoggerLogTypes.ERROR, {
class_name: "LaunchServiceImpl",
function_name: "verifyFellowshipCode",
message: error.message,
stack: error.stack,
additional_info: {
code
}
});
return false;
}
}
I want to write a test case where I can just send the control directly to the catch block to get the false value. This is a very simplistic example but in few other functions I am doing a lot more in the catch block.
My mocha unit test looks like this:
it("should go to the catch block and return false in case of exception", async function() {
let serviceInstance = new MyClass();
let response = await serviceInstance.verifyCode("john#test.com", "abc123");
// how do I directly jump to the catch block here??
expect(response).to.equal(false);
});
Suppose you have a function that will throw an error with the message User not found so you can test like this:
profileServiceInstance.getProfile('1').catch((err) => {
expect(() => {
throw err;
}).to.throw(Error, 'User not found');
});

NodeJS - Properly catch errors of async functions

I was writing a script to pull data from Google Cloud metrics via API when I accidentally discovered that I don't know how to properly catch errors of asynchronous functions. :O
Here is the example code from google cloud:
// Imports the Google Cloud client library
const monitoring = require('#google-cloud/monitoring');
// Creates a client
const client = new monitoring.MetricServiceClient();
/**
* TODO(developer): Uncomment and edit the following lines of code.
*/
const projectId = 'XXXXXXXXX';
async function getMetrics() {
const request = {
name: client.projectPath(projectId),
filter: 'metric.type="cloudsql.googleapis.com/database/cpu/utilization"',
interval: {
startTime: {
// Limit results to the last 20 minutes
seconds: Date.now() / 1000 - 60 * 1,
},
endTime: {
seconds: Date.now() / 1000,
},
},
// Don't return time series data, instead just return information about
// the metrics that match the filter
view: 'HEADERS',
};
// Writes time series data
console.log('start')
const [timeSeries] = await client.listTimeSeries(request);
console.log('Found data points for the following instances:');
timeSeries.forEach(data => {
console.log(data.metric.labels.instance_name);
});
}
getMetrics();
The function listTimeSeries returns a promise. I got an error that I need to be authenticated to perform that action, no problem there.
The issue is that I couldn't catch that error.
I tried surrounding the call with try {...} catch (err) {...} block, wasn't caught.
I tried to catch it like this const [timeSeries] = await client.listTimeSeries(request).catch(console.log); - No luck there.
I must be missing something because I'm pretty new to nodeJS and no way catching errors from async functions is not supported.
I'm using nodeJS v14.
What am I missing guys?
Thank you in advance!
EDIT
As requested (by #CherryDT), here is the full error output:
I hope its not too blurry.
EDIT
It turns out that the way I've been trying to catch errors is fine.
The issue occurred because of listTimeSeries function (from an external library), which threw an error instead of rejecting the promise, which is impossible to catch.
Thanks, guys.đź‘Ť
Note that I refer to "async functions" and "asynchronous functions." In Javascript "async function" means a function created with the async keyword, whereas when I say "asynchronous function" I mean in the traditional sense, any function that runs asynchronously. In Javascript, functions created with the async keyword are actually just promises under the hood.
Your code would work if errors thrown from asynchronous functions (inside promises) could be caught. Unfortunately, they can't. Unless the function is using the async function syntax, errors in promises must be wrapped with reject. See the MDN example for the gotcha we're looking at here:
// Throwing an error will call the catch method most of the time
var p1 = new Promise(function(resolve, reject) {
throw new Error('Uh-oh!');
});
p1.catch(function(e) {
console.error(e); // "Uh-oh!"
});
// Errors thrown inside asynchronous functions will act like uncaught errors
var p2 = new Promise(function(resolve, reject) {
setTimeout(function() {
throw new Error('Uncaught Exception!');
}, 1000);
});
p2.catch(function(e) {
console.error(e); // This is never called
});
// Errors thrown after resolve is called will be silenced
var p3 = new Promise(function(resolve, reject) {
resolve();
throw new Error('Silenced Exception!');
});
p3.catch(function(e) {
console.error(e); // This is never called
});
I believe this is the code in the library that's throwing the error, below. Notice that another error is being properly rejected. All comments are mine.
for (const methodName of metricServiceStubMethods) {
const callPromise = this.metricServiceStub.then(
stub => (...args: Array<{}>) => {
if (this._terminated) {
// This is the right thing to do!
return Promise.reject('The client has already been closed.');
}
const func = stub[methodName];
return func.apply(stub, args);
},
(err: Error | null | undefined) => () => {
// If this was an async function (as in, using the keyword async,
// not just literally an asynchronous function), this would work,
// because the async keyword is just syntactic sugar for creating
// a promise. But it's not so it can't be caught!
throw err;
}
);
I believe, in this case, unfortunately there's no way for you to catch this error.
You can do this.
(async function() {
try {
await getMetrics();
} catch(error) {
console.log("Error occured:", error);
}
})();
Please note that if you are trying to catch the error in Promise you can use .then(() => { }).catch(err => { }) style, but for async/await you will need try { } catch(err) { } style to catch the error.
Edit
By doing this, it must catch any errors if the promise become rejected. If you still cannot catch the error, this means that the library you're using doesn't reject the promise properly (Promise.reject()), instead it did hard-coded throw error inside the promise instead of rejecting one. For this case you can't do anything with error catching.

How to test component that should throw an error?

My component throw error (and should be)
function ProblemChild() {
throw new Error("Error thrown from problem child");
return <div>Error</div>; // eslint-disable-line
}
My question is .. how can I test this??
const wrapper = mount(<ProblemChild />);
Something like this..
expect(wrapper).toThrow()
Wrap mounting within callback. Like so:
expect(() => mount(<ProblemChild />)).toThrow()
You can do something like this:
it('should throw an error when there is an error', () => {
try {
wrapper.setState({error: true});
expect(true).toBe(false); //Fail test if no error is thrown
} catch (error) {
expect(error).toBe(error); //Pass test if an error is thrown
}
});
In the example above i have the error in the state. It can be possible that your function is called onClick or onChange. In that case you have to use a simulate event before the assertion.

delay before co() finished

I try make some node script with co. It works well, but a have big delay before script finished. (I got "Ok" or "Bad" after one second, but script finishes 7 seconds after it). What I missed?
co(function *() {
let errors = yield someCheck(process.argv);
if (!errors) {
console.log('Ok');
} else {
console.log('Bad');
}
})(function(e) {
if (e) {
console.log(e);
}
});
I get a typeError when I run your code. I'm not sure what you are trying to do there, but I think you cannot pass the error handler as a second argument when calling co(), you have to use then() or catch() for error handling.
// Here's an example
co(function*() {
var result = yield Promise.resolve(true);
return result;
}).then(function(value) {
console.log(value);
}, function(err) {
console.error(err.stack);
});
// you can also catch the error
co(function *(){
// yield any promise
var result = yield Promise.resolve(true);
}).catch(onerror);
function onerror(err) {
// log any uncaught errors
// co will not throw any errors you do not handle!!!
// HANDLE ALL YOUR ERRORS!!!
console.error(err.stack);
}
I think process.exit() would fix your issue.

How do I properly test promises with mocha and chai?

The following test is behaving oddly:
it('Should return the exchange rates for btc_ltc', function(done) {
var pair = 'btc_ltc';
shapeshift.getRate(pair)
.then(function(data){
expect(data.pair).to.equal(pair);
expect(data.rate).to.have.length(400);
done();
})
.catch(function(err){
//this should really be `.catch` for a failed request, but
//instead it looks like chai is picking this up when a test fails
done(err);
})
});
How should I properly handle a rejected promise (and test it)?
How should I properly handle a failed test (ie: expect(data.rate).to.have.length(400);?
Here is the implementation I'm testing:
var requestp = require('request-promise');
var shapeshift = module.exports = {};
var url = 'http://shapeshift.io';
shapeshift.getRate = function(pair){
return requestp({
url: url + '/rate/' + pair,
json: true
});
};
The easiest thing to do would be to use the built in promises support Mocha has in recent versions:
it('Should return the exchange rates for btc_ltc', function() { // no done
var pair = 'btc_ltc';
// note the return
return shapeshift.getRate(pair).then(function(data){
expect(data.pair).to.equal(pair);
expect(data.rate).to.have.length(400);
});// no catch, it'll figure it out since the promise is rejected
});
Or with modern Node and async/await:
it('Should return the exchange rates for btc_ltc', async () => { // no done
const pair = 'btc_ltc';
const data = await shapeshift.getRate(pair);
expect(data.pair).to.equal(pair);
expect(data.rate).to.have.length(400);
});
Since this approach is promises end to end it is easier to test and you won't have to think about the strange cases you're thinking about like the odd done() calls everywhere.
This is an advantage Mocha has over other libraries like Jasmine at the moment. You might also want to check Chai As Promised which would make it even easier (no .then) but personally I prefer the clarity and simplicity of the current version
As already pointed out here, the newer versions of Mocha are already Promise-aware. But since the OP asked specifically about Chai, it's only fair to point out the chai-as-promised package which provides a clean syntax for testing promises:
using chai-as-promised
Here's how you can use chai-as-promised to test both resolve and reject cases for a Promise:
var chai = require('chai');
var expect = chai.expect;
var chaiAsPromised = require("chai-as-promised");
chai.use(chaiAsPromised);
...
it('resolves as promised', function() {
return expect(Promise.resolve('woof')).to.eventually.equal('woof');
});
it('rejects as promised', function() {
return expect(Promise.reject('caw')).to.be.rejectedWith('caw');
});
without chai-as-promised
To make it really clear as to what's getting tested, here's the same example coded without chai-as-promised:
it('resolves as promised', function() {
return Promise.resolve("woof")
.then(function(m) { expect(m).to.equal('woof'); })
.catch(function(m) { throw new Error('was not supposed to fail'); })
;
});
it('rejects as promised', function() {
return Promise.reject("caw")
.then(function(m) { throw new Error('was not supposed to succeed'); })
.catch(function(m) { expect(m).to.equal('caw'); })
;
});
Here's my take:
using async/await
not needing extra chai modules
avoiding the catch issue, #TheCrazyProgrammer pointed out above
A delayed promise function, that fails, if given a delay of 0:
const timeoutPromise = (time) => {
return new Promise((resolve, reject) => {
if (time === 0)
reject({ 'message': 'invalid time 0' })
setTimeout(() => resolve('done', time))
})
}
// ↓ ↓ ↓
it('promise selftest', async () => {
// positive test
let r = await timeoutPromise(500)
assert.equal(r, 'done')
// negative test
try {
await timeoutPromise(0)
// a failing assert here is a bad idea, since it would lead into the catch clause…
} catch (err) {
// optional, check for specific error (or error.type, error. message to contain …)
assert.deepEqual(err, { 'message': 'invalid time 0' })
return // this is important
}
assert.isOk(false, 'timeOut must throw')
log('last')
})
Positive test is rather simple. Unexpected failure (simulate by 500→0) will fail the test automatically, as rejected promise escalates.
Negative test uses the try-catch-idea. However: 'complaining' about an undesired pass happens only after the catch clause (that way, it does not end up in the catch() clause, triggering further but misleading errors.
For this strategy to work, one must return the test from the catch clause. If you want't to test anything else, use another it()-block.
Thre is a better solution. Just return the error with done in a catch block.
// ...
it('fail', (done) => {
// any async call that will return a Promise
ajaxJson({})
.then((req) => {
expect(1).to.equal(11); //this will throw a error
done(); //this will resove the test if there is no error
}).catch((e) => {
done(e); //this will catch the thrown error
});
});
this test will fail with following message: AssertionError: expected 1 to equal 11

Resources