One of the points of using NodeUnit is to write new functions and test them often. Problem is, if one of the tested functions throws an error (including JS runtime errors), the error is not shown to the user.
Here is the simplest possible test case: (Note that a.b.c.d will cause a runtime error)
exports.all = {
one: function( test ){
test.done();
},
two: function( test ){
as( function( err, res ){
test.done();
});
},
}
function as( callback ){
process.nextTick( function() {
a = testMe();
callback( null, a );
});
}
function testMe(){
a.b.c.d.e = 100;
return 10;
}
However, testMe() might be a new function I am developing. An uninitialised variable, or anything, will just fall silent.
Add your own uncaught exception handling to your test file either via the uncaughtException event:
process.on('uncaughtException', function(err) {
console.error(err.stack);
});
Or by creating a domain and adding process to it (or whatever event emitter(s) your tests use) so that you can catch the errors that occur within your use of process.nextTick:
var dom = require('domain').create();
dom.add(process);
dom.on('error', function(err) {
console.error(err.stack);
});
Either way, you'll then get console output that looks like:
ReferenceError: a is not defined
at testMe (/home/test/test.js:24:3)
at /home/test/test.js:18:9
at process._tickDomainCallback (node.js:459:13)
Related
I'm trying to validate a model and it's contents. However, because of the structure of loopbacks custom validation functions it's quite difficult to program more advanced logic than simple string validation.
Job.validate('job_definition, function(err){
//err();
//this will succeed in throwing error
Job.app.models.anotherModel.findOne({where:{name:this.job_definition.toolName}}, function(error, tool){
if(tool.aProperty === this.job_definition.aProperty){
//err();
//this will not succeed, validation script will exit before err() is thrown
}
});
}, {message: 'this is malformed'});
How can I get this validation function to 'wait' before exiting?
Here is an example using validateAsync (https://apidocs.strongloop.com/loopback-datasource-juggler/#validatable-validateasync). Note that you have to run err() when you want to fail validation.
module.exports = function(Person) {
function myCustom(err, done) {
console.log('async validate');
var name = this.name;
setTimeout(function() {
if(name == 'Ray') {
err();
done();
} else {
done();
}
}, 1000);
}
Person.validateAsync('name', myCustom, {message:'Dont like'});
};
Does this make sense? FYI, I could rewrite that if a bit nicer.
I'm running some tests using Mocha and chai. One of the tests is hanging at a .should.be.deep.equal call.
Here's the test code:
// Make the fake connection before running tests
before(function(done) {
mongoose.connect('mongodb://fake.test/TestingDB', function(err) {
done(err);
});
});
// Test Cases
describe('Testing the functions that deal with users and locations:', function() {
// Test Setup
var req = {};
beforeEach(function(done) {
mockgoose.reset()
async.parallel([function(callback){
sensors.create(testData.deviceData, function(err, model) {
if (err) {console.log(err)}
callback();
});
}, function(callback) {
locations.create(testData.locationData, function(err, model) {
if (err) {console.log(err)}
callback();
});
}], function(err) {
done();
});
});
afterEach(function(done) {
mockgoose.reset();
done();
});
// Tests
describe('function locationList', function() {
it('should list the test location', function(done) {
dbFunctions.locationList(req, function(result) {
console.log(result) //Prints the whole result
console.log(testData.locationList)
result.should.exist; //This doesn't cause it to hang
result.should.be.deep.equal(testData.locationList) //hangs here
done(result);
});
})
})
});
And here's the function it's testing:
exports.locationList = function(req, callback) {
listLocations().then(
function(data) {
callback(data);
},
function(err) {
console.log('Error Retrieving Location Information: ' + err);
callback(err);
});
};
As I note in the comments, the results object exists and gets printed to the console. results.should.exist; doesn't throw an exception and if I comment out everything but it the test works fine. For some weird reason, despite both the testData.locationList and result object existing, the test times out. I have 14 other tests that use the exact same syntax without any problems. Does anyone know what could be causing this to happen for this specific test?
Here's the output from the tests:
Testing the functions that deal with users and locations:
function locationList
[ { devices: {},
address: '123 Fake St, Waterloo, On',
location: 'Unittest',
owner: 'unit#test.com',
_id: '-1' } ]
[ { devices: {},
address: '123 Fake St, Waterloo, On',
location: 'Unittest',
owner: 'unit#test.com',
_id: '-1' } ]
1) should list the test location
0 passing (2s)
1 failing
1) Testing the functions that deal with users and locations: function locationList should list the test location:
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
at null.<anonymous> (C:\Users\My Name\AppData\Roaming\npm\node_modules\mocha\lib\runnable.js:189:19)
Extending the timeout doesn't work. Nor does putting something random (ie. the integer 1 in the .should.be.deep.equal() function.
My guess is that the callback in exports.locationList is called synchronously, and that your test case is actually failing, throwing an exception, which never gets caught because the callback is (synchronously) called from within a promise handler (more info here).
Try and see if this works better:
dbFunctions.locationList(req, function(result) {
setImmediate(function() {
console.log(result) //Prints the whole result
console.log(testData.locationList)
result.should.exist; //This doesn't cause it to hang
result.should.be.deep.equal(testData.locationList) //hangs here
done(result);
});
});
// or use a module like dezalgo (https://github.com/npm/dezalgo)
The underlying cause may be mockgoose.
Also, you're not using the proper Node.js conventions where the first argument to a callback function is "reserved" for errors. In other words, your code should look similar to this:
if (err) {
callback(err);
} else {
callback(null, data);
}
You're now passing both errors and data as the first argument.
A guess: Maybe the issue lies with mongoose decorating the result with it's own functions/members, and one of them somehow getting stuck in an infinite loop when chai tries to enumerate them all to perform a deep comparison.
I am working with MongoDB using Mongoose. Most of the opeartion works with callback. An error may occur while saving/updating/finding a document. Though we can always check if there an error in callback function (as shown in below code) but I want to know while developing how can we generate error and test these blocks?
Tank.findById(id, function (err, tank) {
if (err) return handleError(err);
tank.size = 'large';
tank.save(function (err) {
if (err) return handleError(err);
res.send(tank);
});
});
Are you familiar with the Error class? Emiting errors with the EventEmitter? Throwing errors with throw?
This link is a fairly extensive overview on how to deal with errors in node.
Assuming your using express, in the case of the example you provided, I would usually create an instance of the Error class doing something like:
exports.findTankById = function(req, res, next) {
var id = req.params.id;
Tank.findById(id, function (err, tank) {
if (err) {
var e = new Error("Failed to find tank");
e.data = err;
// attach other useful data to error class instance
return next(e);
}
return res.status(200).json({ data: tank });
})
});
Then in another part of the application, have a middleware function that catches errors passed by your routes via next(). That function could log the error or doing something more creative. Note that when using new Error(...) you can access the stack using the stack attribute of the Error class (e.g. err.stack). After processing the error, the error handler function would send back an appropriate response.
A simple error handler function could look something like:
app.use(function (err, req, res, next) {
if(err.data) {
// caught operational errors
/* do something to log or process error */
var response = {
type : 'error',
description : err.message // this would use "Failed to get tank" for above example
};
res.status(500).json(response);
} else {
// unexpected errors
var domainThrown = err.domain_thrown || err.domainThrown;
var msg = 'domainThrown: ' + domainThrown + '\n' + err.stack;
console.error('%s %s\n%s', req.method, req.url, msg);
res.set('Connection', 'close');
res.setHeader('content-type', 'text/plain');
res.status(503).send(msg + '\n');
}
});
If you like this approach, I usually define more specific error objects of my own that more or less extend the Error class. Using functions to create the more specific error types limits the need to write out the
var e = new Error("Failed to find tank");
e.data = err;
/* attach other useful data to error class instance */
part every time. Using more specific error objects also forces consistency on how the errors are being formatted. Hope that is helpful,
Craig
I have the following piece of code, but due to it's async behavior (I suppose), the callback is called twice. I'm using node-unzip to unzip a file I download from the internet.
function DownloadAndExtract(file, callback) {
log.debug("Starting download of "+file);
fse.ensureDirSync(tempPath);
var extractor = unzip.Extract({path: tempPath});
extractor.on("close", function() {
log.debug("Done downloading "+file);
return callback(null, file);
});
extractor.on("error", function (err) {
log.error("Extracting download "+file+": "+JSON.stringify(err, null, "\t"));
return callback(err, null); // THIS IS LINE 274
});
var url = "";
if(file == "WONEN") {
url = "https://example.com/file1.zip";
}else if(file == "BOG") {
url = "https://example.com/file2.zip";
}
if(url != "") {
request
.get(url)
.on("error", function (err) {
return callback(err, null);
})
.pipe(extractor);
}else{
return callback(new Error("Invalid file indicator: '"+file+"'"), null);
}
}
I expected return to actually quit all running async functions but that is obviously nonsense. Now, the error I keep getting is the following:
/home/nodeusr/huizenier.nl/node_modules/async/lib/async.js:30
if (called) throw new Error("Callback was already called.");
^
Error: Callback was already called.
at /home/nodeusr/huizenier.nl/node_modules/async/lib/async.js:30:31
at /home/nodeusr/huizenier.nl/node_modules/async/lib/async.js:251:21
at /home/nodeusr/huizenier.nl/node_modules/async/lib/async.js:575:34
at Extract.<anonymous> (/home/nodeusr/huizenier.nl/realworks.js:274:10)
at Extract.emit (events.js:129:20)
at Parse.<anonymous> (/home/nodeusr/huizenier.nl/node_modules/unzip/lib/extract.js:24:10)
at Parse.emit (events.js:107:17)
at /home/nodeusr/huizenier.nl/node_modules/unzip/lib/parse.js:60:12
at processImmediate [as _immediateCallback] (timers.js:358:17)
The output of the log.error() call is the following:
21-02-2015 03:00:05 - [ERROR] Extracting download WONEN: {} so I'm quite confused. There isn't really an error, then why is the event emitted?
How would I prevent the callback from being called twice here? Contacting the creator of the package or create a work around?
Code calling DownloadAndExtract
async.parallel([
function (callback) {
DownloadAndExtract("WONEN", callback);
},
function (callback) {
DownloadAndExtract("BOG", callback);
}
],
function (err, done) {
if(err) return log.error("Update entries: "+JSON.stringify(err, null, "\t"));
// Do different logic if no error
});
Edit
One of my attempts is declaring a var callbackAlreadyCalled = false within the function, and at any given point where I call the callback, I do
if(!callbackAlreadyCalled) {
callbackAlreadyCalled = true;
return callback(callback params);
}
Is this a good approach or could I handle it in a better way?
Edit 2
Already found out that the empty error is caused by errors not working properly when using JSON.stringify(), however, problem doesn't change.
It looks like you have waterfall error calls.
When you call the request, it calls the extractor then if there is an error it calls the on_error of the extractor and then the on_error of the request.
Try to unify your returns from errors or call it only once. Make a test removing one of your "on('error'" validation.
With synchronous errors, you can nest error scopes like this:
try {
try {
throw Error('e')
} catch(e) {
if(e.message !== 'f')
throw e
}
} catch(e) {
handleError(e)
}
This is how I would expect it to work, but it doesn't (seems an error inside a domain error handler is thrown up to the top, skipping any domains in between):
var domain = require('domain');
var dA = domain.create();
dA.on('error', function(err) {
console.log("dA: "+ err); // never happens
});
dA.run(function() {
var dB = domain.create();
dB.on('error', function(err) {
throw err
});
dB.run(function() {
setTimeout(function() {
console.log('dB')
throw 'moo'
},0)
});
});
Is there a way to do this right?
Bubbling doesn't work in domains through rethrowing. If you want to pass an error off to another domain you know can handle an error, you can re-emit the error event on that domain directly:
var domain = require('domain');
var dA = domain.create();
dA.on('error', function(err) {
console.log("dA: "+ err); // never happens
});
dA.run(function() {
var dB = domain.create();
dB.on('error', function(err) {
dA.emit('error', err);
});
dB.run(function() {
setTimeout(function() {
console.log('dB')
throw 'moo'
},0)
});
});
To expand a little, the problem with throwing from a domain's error handler is that it propagates directly to the top level, and even more confusingly, if the throw is the result of an error in the error handlier, is that the stacktrace that gets printed out is from your original error, not the new error in the handler. Theoretically it would be possible to bubble exceptions up the stack, but that's not how domains were designed.
The "nested" domains will work properly if the handler of an outer domain throws while an inner domain is active, but what it does in that case is give the error to the outer domain's error handler and then exits both the outer and the nested domain. This mimics how a catch unwinds the stack in the try/catch case, but it can be a little confusing.