Testing Errors passed back with Callbacks with mocha and should - node.js

UPDATE 2-July-2013
Using the last approach with the wrapped anonymous function can introduce some strange testing behavior. If you expect a thrown error, but there is an error in the actual execution the test will pass, but will pass on any error and probably not the one you were expecting. I do two things, if its an error i am specifying i ensure the error text returned from the stub and the thrown error are the same. If it is from a third party library where i don't necessarily know the errors i will put some arbitrary value so that i know the passes are specific to the units i am testing. Example using sinon.js to stub some shizzle.
it('should return an error if the save fails', function(){
stubCall.restore();
stubCall = sinon.stub(Provider, 'save').callsArgWith(1, new Error('Save Error'));
(function(){
User.create(fakeUser, function(err, user){
if(err)
throw err;
});
}).should.throw("Save Error");
});
I am getting into Node.js and trying to do some Behaviour Driven Development(BDD) with Mocha and should.js (and sinon in there as well at some point).
One thing i am having problems with is testing errors returned by a call back, for instance this simple test:
it('should return an error if you try and execute an invalid Query object', function () {
var stubQuery = {};
Provider.execute(stubQuery, function (err) {
//should.not.exist(err);
should.have.property('message', 'Invalid Query Object');
});
});
with the function:
PostgresqlProvider.prototype.execute = function (query, cb) {
};
It doesn't matter what i try and test, the test always passes (should.exist, etc), and the only way i can get it to fail first is by adding cb(null); into the execute function, which kinda goes against what i am doing as i am trying to test before adding behaviour, not adding behaviour to fail a test.
I know i am doing some real newb mistake here, probably on a few levels but i am not grasping the testing of an error passed as a call back with is not thrown (which i am sure i could test easier)!
UPDATE
Using the code from Herman, i adjusted and that indeed words (the test is failing with no behavior defined)
it('should return an error if you try and execute an invalid Query object', function () {
var stubQuery = {};
Provider.execute(stubQuery, function (err) {
if (err) throw new Error(err);
}).should.throw();
});
issue now is i can't "catch" the error if sent in the callback to pass the test, the should.throw method doesn't get called, it just states that expected a throw to happen but didnt even though i am returning an error to the callback from my method. This could be down to scope
but i am unsure if i should add some form of closure, and at what level.
UPDATE 2
Figured it out, i needed to wrap the call to the function inside a closure, not the callback (Doh!) and then place the assert (should.throw) at the end of the closure;
it('should return an error if you try and execute an invalid Query object', function () {
var stubQuery = {};
(function(){
Provider.execute(stubQuery, function (err) {
if (err)
throw err;
});
}).should.throw();
});

You need to generate an exception inside your test function for the test to fail.
I'd add if (err) throw "Invalid query error " + err this way:
it('should return an error if you try and execute an invalid Query object', function () {
var stubQuery = {};
Provider.execute(stubQuery, function (err) {
if (err) throw "Invalid query error " + err
});
});
That should do.
(ON UPDATE)
should.throw() did not work for me either... I did this dirty hack to work the flow:
it('should return an error if you try and execute an invalid Query object', function () {
var stubQuery = {};
try {
Provider.execute(stubQuery, function (err) {
if (err) throw "Invalid query error " + err
});
// No error, keep on the flow
noError ();
} catch (e) {
// Error, let's continue the flow there
error (e)
}
function noError () {
// The flow in the event we have no error
// ... //
}
function error (errorObj) {
// The flow, in the event we have error
// ... //
}
});
Hope this helps!

Related

mongodb trying to drop a collection that might not exist

I'm getting:
(node:78465) UnhandledPromiseRejectionWarning: MongoError: ns not found
My code:
delete(projectId) {
if (!this.db) return
this.db.collection(projectId, (err, collection) => {
// err is null here.
collection.drop();
});
}
How do I make sure I don't get the error if the collection doesn't exist.
Use promises:
this.db.collection(projectId).drop().then(function () {
// success
}).catch(function () {
// error handling
})
So ns namespace error would come up when trying do something with a collection that does not exists. With your code you could do:
delete(projectId) {
if (!this.db) return;
this.db.collection(projectId).drop((err, dropOK) =>{
if (err) {
console.error("There is an error::", err);
return;
}
if (dropOK) console.log("Collection deleted");
});
}
Some points from understanding the problem:
db.colletion(anyString) would just return the interface with
anyString sitting inside the namespace & all the action definitions
that you might want to execute with that collection.
So with error first callback on db.collection(anyString, (e,res)) e will be null only and result would be passed to res as long as db is alive.
And that is why db.collection() itself is not an async function and the callback isn't really needed.
It is the action like collection.drop() that is async goes to drop it but doesn't find it and hence the error.
And drop() is the actual one that needs a callback with error to identify.

How to resolve next is not a function error message?

I am trying to create a MEAN stack app, I'm currently working on the UPDATE functionality.
My code is currently failing when it runs into this method:
businessRoutes.route('/update/:id').post(function (req, res) {
Business.findById(req.params.id, function (err, business) {
if (!business)
return next(new Error('Could not load Document'));
else {
business.person_name = req.body.person_name;
business.business_name = req.body.business_name;
business.business_gst_number = req.body.business_gst_number;
business.save().then(business => {
res.json('Update complete');
console.log('Update Complete');
})
.catch(err => {
res.status(400).send("unable to update the database");
});
}
});
});
The error message being displayed in the console is:
TypeError: next is not a function
It's failing on this line of code:
return next(new Error('Could not load Document'));
Can someone please tell me why this is occurring & how I can resolve it?
The second parameter to findById expects a callback that has two arguments, err and <entity>. There's no middleware or something else in place, what you call next(...) tries to call your found entity.
From the docs
Adventure.findById(id, function (err, adventure) {});
You see, in your case, business is always undefined, and adventure or next is never a function.
Here is what you probably meant:
The problem is that the param that you mean to be "next" actually comes from express.js (which means it comes in on the first function call back that you ran (after .post(---this function--- has 3 params that you may chose to use:
req the request that the user made to your server
res the response that you are getting ready to send
next the 3rd param is optional and allows you to send to the next middleware or if you send it a parameter it will send it to the error handler which will send your error as a response.
On the other hand:
you placed the next param randomly in the middle of the mongoose function that you attempted to call: the mongoose function actually only takes (err, item)...
businessRoutes.route('/update/:id').post(function (req, res, next) {
// note that express exposes the "next" param
// this is a callback indicating to run the `next` middleware
Business.findById(req.params.id, function (err, business, whoKnows) {
// note this is from mongoose.js: the callback function actually only has 2 parameters (err, and foundObject)
if (!business)
return next(new Error('Could not load Document'));
else {
business.person_name = req.body.person_name;
business.business_name = req.body.business_name;
business.business_gst_number = req.body.business_gst_number;
business.save().then(business => {
res.json('Update complete');
})
.catch(err => {
res.status(400).send("unable to update the database");
});
}
});
});
Be advised, that I'd recommend using async await and that would make the whole thing much easier to understand:
businessRoutes.route('/update/:id').post(async (req, res, next) => {
try {
const foundBusiness = await Business.findById(req.params.id)
//... do stuff
catch (e) {
next(throw new Error(e))
// .. other stuff
}
})

Unit testing with Supertest, Mocha & Sinon timing out

I am trying to write a unit/integration test where I want to get a list of things in the database. For not it is only a GET, but these tests needs to extend to POST, PUT & DELETE.
The code I have thus far works fine, I can actually get data from the DB, but as soon as I try to stub out the function which is responsable for making the call to the DB, Mocha times out
1 failing
1) /account_types GET 200 List:
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
at null. (C:\Code\JS\general_admin_service\node_modules\mocha\lib\runnable.js:215:19)
I understand the done() callback isn't being called because the code is stuck somewhere, however, I do not understand what I am doing wrong.
I used the following references to get where I am:
Testing With Mocha, Sinon.js & Mocking Request
Lessons learned from unit testing with Sinon.JS
My code is as follows:
The Test:
'use strict';
var expect = require('chai').expect,
request = require('supertest'),
chance = require('chance').Chance(),
server = require('../server'),
sinon = require('sinon'),
select = require('../../helpers/data_access/select');
describe("/account_types", function () {
before(function(done){
sinon
.stub(select, "query_list")
.returns([{id: "test"}]);
done();
});
after(function(done){
select
.query_list
.restore();
done();
});
it('GET 200 List', function (done) {
request(server.baseURL)
.get('/api/v1/account_types')
.set('Accept', 'application/json')
.expect('Content-Type', 'application/json')
.expect(200)
.end(function (err, res) {
/* istanbul ignore if */
if (err)
return done(err);
expect(res.body).to.include.keys('result');
expect(res.body.result).to.not.be.null;
expect(res.body.result).to.not.be.undefined;
expect(res.body.result).to.be.an('Array');
expect(res.body.result.length).to.be.above(0);
//expect(select.query_list).to.have.been.calledOnce;
return done();
});
});
});
Restify endpoint:
var select = require('../helpers/data_access/select')
module.exports = function (server) {
var query = "..."
return select.query_list(res, next, db_config, query);
});
};
select.js:
var sql = require('mssql');
module.exports = {
query_list: function (res, next, config, sql_query) {
return query(res, next, config, sql_query, true);
},
query_single: function (res, next, config, sql_query) {
return query(res, next, config, sql_query, false);
}
};
function query(res, next, config, sql_query, isList) {
var connection = new sql.Connection(config);
connection.connect(function (err) {
if (err) {
return on_error(err, res);
}
var request = new sql.Request(connection);
request.query(sql_query, function (err, response) {
connection.close();
if (err) {
return on_error(err, res);
}
if (isList) {
return return_list(res, response, next);
} else {
return return_single(res, response, next);
}
});
});
}
function on_error(error, res, next) {
res.status(500).send(error);
return next();
}
function return_list(res, response, next) {
res.send({result: response});
return next();
}
function return_single(res, response, next) {
res.send({result: response[0]});
return next();
}
What I expect to happen is that because I stub out the query_list function, should I wish to put a console.log(res.body.result); after the expect's I have in place, I should see a return of [{id: "test"}], but it is obviously not getting to that point.
What am I doing wrong?
UPDATE: Added the full select.js file.
As you already make clear in the comments, it's difficult to test code that's deeply nested.
It's usually much better to work with callbacks or promises, so that each piece of your app will handle the part it's responsible for, but not (much) more. So your route handler will handle the request and the response. It's obviously okay to call other functions, like ones that perform database queries, but instead of letting those functions send back a response, you use callbacks that "call back" to the route handler with the query results.
Something like this:
server.get('/api/v1/account_types', function(req, res, next) {
select.query_list(QUERY, function(err, records) {
if (err) return next(err);
res.send({ results : records });
next();
});
});
In terms of using Sinon to test something like this: it really depends on the exact implementation. I can provide a quick example on how to stub the above usage of select.query_list, to make sure that the response contains the correct data.
The basic stub looks like this:
sinon.stub(select, 'query_list').yieldsAsync(null, [ { id : 'test' } ]);
What this does, is when select.query_list() gets call, it will call the first callback argument it receives (it does this by checking each argument to see which is a function) with the arguments null, [ { id : 'test' } ].
Those are the err and records arguments of the callback function passed in the handler. So you can use this to skip the database query entirely and pretend that the query yielded a particular array of records.
From there, res.send() gets called (which was the issue that you initially ran into: it didn't get called at all because it was being performed in a part of your app that wasn't getting called because of your stub) and you can check in your test if the resulting response data is as expected.
It becomes a bit more complicated if you want to stub a function deeper in the call stack, but with the correct Sinon tools (like .yields*, or using spies instead of stubs) it's usually not terribly difficult (provided that all the functions that you want to stub/spy are accessible, that is, exported).

Mocha async callback and Sequelize promise are not synchronize?

I'm writing test cases with mocha and its hook beforeEach which deletes and re-creates all tables using sequelize.drop and sequelize.sync.
lib/testutils.js
exports.deleteAll = function () {
return sequelize.drop({logging: false, cascade: true}).then(function() {
return sequelize.sync({logging: false});
});
};
test/controllers/controllers.js
(A) This did work:
var testutils = require("../../lib/testutils");
describe("CRUD Controller", function() {
beforeEach(function(done) {
testutils.deleteAll().then(function(){
done();
}).catch(function(err) {
return done(err);
});
});
describe("#read()", function(){
it("should....", function(done) {
});
});
}
(B) This did not work:
var testutils = require("../../lib/testutils");
describe("CRUD Controller", function() {
beforeEach(function(done) {
testutils.deleteAll().then(done).catch(function(err) {
return done(err);
});
});
describe("#read()", function(){
it("should....", function(done) {
});
});
}
I don't understand why testutils.deleteAll().then(done) did not work and the first test case it("should....", function(done) did not wait for the beforeEach hook finished. I was getting TypeError: Converting circular structure to JSON. However, testutils.deleteAll().then(function(){ done(); }) did work.
My question is why (B) is not working while (A) is working? Any idea? Is there something wrong?
Mocha supports promises in beforeEach so just write below code:
beforeEach(function(){
return testutils.deleteAll();
});
Your deleteAll() function returns sequelize.sync() which returns a promise passing it this. I think the context will be the last model created, so when you pass a function to the returned promise, the function will be passed the last table created. So in case (A) you specified the function()
and called done() manually without passing any args to done(). But in case (B) when you pass done to the promise.then(), the last model created will be passed to done.
To get what I mean consider the following:
User.create({name: 'aName'}).then(function(user){console.log(user)});
is equivalent to
User.create({name: 'aName'}).then(console.log);
The user will be automatically passed to console.log().
So done was being passed the last model created which I think might cause a problem.
When you pass done directly it receive object returned from deleteAll, and mocha treat it as an error, so it try apply JSON.stringify to this object and show it as an error, but somehow JSON.stringify can't do it, so it throw exception which you can see.
You can clearly see it in Mocha::Runnable#run

making mongoskin function is asynchronous sync

User.prototype.isUnique = function () {
var result;
db.user.count({name:'abcdefg'}, function(err, count){
console.log('the count is: ' + count);
result = count;
});
return result;
}
this function is used to find out if this user is unique, but the return result is always 'undefined' since db operation is async. I believe it is a common problem in nodejs.
How do I solve it?
I try to use promise (https://npmjs.org/package/promise), but I don't have enough knowledge to understand their documentation. It's for very advanced developer.
Can you help me with some sample code? All I need to do is get the count() using mongoskin, and then return the result in this member method.
Thanks!
It's not a common problem, it's just how Node works and something that you'll have to get used to dealing with.
The simplest solution (also a very common one) is to provide a callback function from the calling code. That function will be called whenever the asynchronous action is finished:
User.prototype.isUnique = function(callback) {
db.user.count({name:'abcdefg'}, function(err, count){
console.log('the count is: ' + count);
callback(err, count);
});
}
// in your calling code:
user.isUnique(function(err, count) {
if (err) ...; // TODO: handle error
...
});
It's also common in Node to have callback functions accept at least one argument which contains an error object if an error occurred, or null if everything went okay.
In the code above I'm passing any errors that may have occurred during the call to db.user.count to the callback function. It's up to the calling code to deal with any errors.
There are several alternatives to dealing with asynchronous code. One would be to use promises, like you mention. There are also solutions like streamline that make asynchronous code look like it is synchronous, but it requires that you 'compile' your code (although this can also be done at runtime) before you can use it.
EDIT: if you want to use promises, you can use this:
var Promise = require('promise');
User.prototype.isUnique = function() {
return Promise(function(resolve, reject) {
db.user.count({name:'abcdefg'}, function(err, count){
if (err) {
console.log('an error occurred:', err);
reject(err);
} else {
console.log('the count is:', count);
resolve(count);
}
});
});
};
// in your calling code:
user.isUnique().then(function(count) {
...
}, function(err) {
...
});
(this requires that the promise package is installed)

Resources