I am new to Mocha, and only a little experience with Node/Express. My DbProvider module works perfectly (mongodb) when I am access it through my Express app. And now I want to test it. I have read the Mocha site and some tutorials I could find. But I have big trouble of finding an real-world example out there that I could follow (any links much appreciated!).
Here is my unsuccessful attempt to write a testfile:
var DbProvider = require('../db').DbProvider;
var assert = require('assert');
var dbProvider = new DbProvider('localhost', 27017, 'mydb');
var util = require('util');
console.log(util.inspect(dbProvider));
describe('DbProvider', function(){
describe('findAllNotes', function(){
it('should return some notes', function(){
dbProvider.findAllNotes({}, function (err, result){
assert(result.length > 0);
});
})
})
})
The output I get is this:
$ mocha
{}
✖ 1 of 1 test failed:
1) DbProvider findAllNotes should return some notes:
TypeError: Cannot call method 'collection' of undefined
at DbProvider.doOperation (/Users/frode/Node/json/db.js:46:11)
at DbProvider.findAllNotes (/Users/frode/Node/json/db.js:56:8)
at Context.<anonymous> (/Users/frode/Node/json/test/test.js:15:18)
(cutting out the rest)
It seems that I am unsuccessful to create the dbProvider. This works perfectly in my app... How can I make this work? (And perhaps also: Is the way I have set it up in general ok?)
Edit: Here is the db.js file:
// Database related
'use strict';
var MongoClient = require('mongodb').MongoClient;
var BSON = require('mongodb').BSONPure;
var ObjectID = require('mongodb').ObjectID;
var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$");
var Validator = require('validator').Validator
var fieldMaxLength = 1024;
//var util = require('util');
var DbProvider = function(host, port, database) {
var dbUrl = "mongodb://"+host+":"+port+"/"+database;
var self = this;
MongoClient.connect(dbUrl, function(err, db) {
self.db = db;
});
};
// Do some basic validation on the data we get from the client/user
var validateParams = function(params, callback) {
// Let´ do a quick general sanity check on the length on all fields
for(var key in params) {
if(params[key].length > fieldMaxLength) callback(new Error('Field ' + key + ' is too long.'));
}
// and the let us check some specific fields better
if (params._id) {
if(checkForHexRegExp.test(params._id)) {
// In case of '_id' we also need to convert it to BSON so that mongodb can use it.
params._id = new BSON.ObjectID(params._id);
} else {
var err = {error: 'Wrong ID format'};
}
}
if(err) callback(err);
}
// Generalized function to operations on the database
// Todo: Generalize even more when user authenication is implemented
DbProvider.prototype.doOperation = function(collection, operation, params, callback) {
validateParams(params, callback);
var operationCallback = function(err, result) {
callback(err, result);
};
this.db.collection(collection, function(err, collection) {
if(operation==='find') {
collection.find().toArray(operationCallback);
} else {
collection[operation](params, operationCallback);
}
});
}
DbProvider.prototype.findAllNotes = function(params, callback) {
this.doOperation('notes', 'find', params, callback);
};
DbProvider.prototype.findNoteById = function(params, callback) {
this.doOperation('notes', 'findOne', params, callback);
};
DbProvider.prototype.saveNote = function(params, callback) {
params.created_at = new Date();
this.doOperation('notes', 'save', params, callback);
};
DbProvider.prototype.deleteNote = function(params, callback) {
this.doOperation('notes', 'remove', params, callback);
};
DbProvider.prototype.findUser = function(params, callback) {
this.doOperation('users', 'findOne', params, callback);
};
exports.DbProvider = DbProvider;
SOLUTION:
After Benjamin told me to handle the async nature of mongodb connecting to the database, and inspired by his suggestion on how to adapt the code, I split the constructor function DbProvider into two parts. The first part, the constructor DbProvider now just saves the db-parameters into a variable. The second part, a new function, DbProvider.connect does the actual async connection. See below.
var DbProvider = function(host, port, database) {
this.dbUrl = "mongodb://"+host+":"+port+"/"+database;
};
DbProvider.prototype.connect = function(callback) {
var self = this;
MongoClient.connect(this.dbUrl, function(err, db) {
self.db = db;
callback();
});
};
So I can now make a Mocha test like this (and async tests also need the "Done" included, like you see in the code below):
var assert = require('assert');
var DbProvider = require('../db').DbProvider;
var dbProvider = new DbProvider('localhost', 27017, 'nki');
describe('DbProvider', function(){
describe('findAllNotes', function(){
it('should return some notes', function(done){
dbProvider.connect(function(){
dbProvider.findAllNotes({}, function (err, result){
assert(result.length > 0);
done();
});
});
})
})
})
Note that the acutal test ("should return some notes") is nothing to be proud of. What I wanted here was to get set up so I am able to test something. Now that I finally acutally can do that, I need to write good tests (something in the the line of having a test database, clear it, test insert a document, test search for a document, and so on...).
And in my Express app, I used to set up the database like this:
var DbProvider = require('./db').DbProvider;
// Setup db instance
var dbProvider = new DbProvider(
process.env.mongo_host || 'localhost',
process.env.mongo_port || 27017,
process.env.mongo_db || 'nki'
);
Now I do the same, but in addition, I call the new connect-function:
// Connect to db. I use (for now) 1 connection for the lifetime of this app.
// And I do not use a callback when connecting here (we do in the testing)
dbProvider.connect(function(){});
Benjamin actually pointed out that it may be ok but not the best practice to have the database set up like this in an Express app. But until I figure out what the best practice really is, I will leave this code as it is. Here is a couple of links reagarding the subject I found (but I have still not concluded of how I will solve it myself):
What's the best practice for MongoDB connections on Node.js? and
[node-mongodb-native] MongoDB Best practices for beginner
If you like, you are very welcome to follow/fork/whatever this project on github. My goal is to get it as production ready I can. The link is
https://github.com/frodefi/node-mongodb-json-server
MongoClient.connect is asynchronous.
From the docs:
callback (function) – this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the initialized db object or null if an error occured.
That means DbProvider.db isn't set yet in the test which is why you're getting undefined.
In here:
MongoClient.connect(dbUrl, function(err, db) {
self.db = db;
});
You're telling it "update self.db after the connection happened", which is at least one event loop tick after this one (but may be more). In your mocha code you're executing your .describe and .it methods right after creating your DbProvider instance which means it was not initialized yet.
I suggest that you re-factor DbProvider to return a callback instead of being a constructor function. Maybe something along the lines of:
var getDbProvider = function(host, port, database,callback) {
var dbUrl = "mongodb://"+host+":"+port+"/"+database;
MongoClient.connect(dbUrl, function(err, db) {
self.db = db;
callback(db);
});
};
Which also means moving all the DBProvider methods to an object (maybe the callback will return a dbprovider object and not just a db?).
Another bug solved by using Unit Tests :)
This is what I used: https://github.com/arunoda/mocha-mongo
It has set of testing helpers for mongodb
Related
I'm still a n00b with NodeJS so this question may be somewhat elementary.
I'm using MongoJS to read two logically related collections. The first find() returns a value that I pass to the second find() to get the information I need.
I've tried several strategies, the last one (snippet #1) being a class that I export.
Before that I just had a function that did a return, returning the desired value, i.e., "config[0]".
In this code all I did was set the "sapConfig" attribute to the word "test", but when I execute this code the value of "sapConfig" is always "null" after I call the "get_config()" method and - strangest of all - the reference to "this.sapConfig = 'test'" generates an error, i.e., "Cannot set property 'sapConfig' of undefined".
When I had the code just as a simple function with a return statement (snippet #2), no errors were generated but the value returned is always "undefined" although the console.log() statements show that the value of the variable being returned has the desired value. What gives?
Code Snippet #1: Returns Object
"use strict";
var mongojs = require('mongojs'); // MongoDB API wrapper
module.exports = function(regKey) {
this.regKey = regKey;
this.sapConfig = null;
this.get_config = function() {
// Read SAP connection information from our MONGO db
var db = mongojs('mongodb://localhost/MIM', ['Configurations','Registrations']);
db.Registrations.find({ key: this.regKey }, function(err1, registration){
console.log('Reg.find()');
console.log(registration[0]);
db.Configurations.find({ type: registration[0].type }, function(err2, config){
console.log('Config.find()');
console.log('config=' + config[0].user);
this.sapConfig = 'test';
});
});
}
this.get_result = function() {
return this.sapConfig;
}
}
Again, the code in snippet #1, when I make a call to "get_config()", results in an error when it executes the line "this.sapConfig = 'test'".
However, after this error I can execute "obj.get_result()" and I get the value to which it was initialized, i.e., null. In other words, that same code doesn't generate an error saying that the "this" is undefined as .in the "get_config()" method
Code Snippet #2: Using the "return" statement
"use strict";
var mongojs = require('mongojs'); // MongoDB API wrapper
module.exports = function(regKey) {
// Read SAP connection information from our MONGO db
var db = mongojs('mongodb://localhost/MIM', ['Configurations','Registrations']);
db.Registrations.find({ key: regKey }, function(err1, registration){
console.log('Reg.find()');
console.log(registration[0]);
db.Configurations.find({ type: registration[0].type }, function(err2, config){
console.log('Config.find()');
console.log('config=' + config[0].user);
return config[0].user;
});
});
}
When I receive the return value and inspect it, it's "undefined". For example, at the Node CL I issue the following commands:
var config = require('./config') // The name of the module above
> var k = config('2eac44bc-232d-4667-bd24-18e71879f18c')
undefined <-- this is from MongoJS; it's fine
> Reg.find() <-- debug statement in my function
{ _id: 589e2bf64b0e89f233da8fbb,
key: '2eac44bc-232d-4667-bd24-18e71879f18c',
type: 'TEST' }
Config.find()
config=MST0025
> k <-- this should have the value of "config[0]"
undefined
You can see that the queries were successful but the value of "k" is "undefined". What's going on here?
I don't care which approach I use I just need one of them to work.
Thanks in advance!
this.sapConfig is not accessible. Thats because this refers to within the current function. What I like todo, is have a variable that refers to the function instance that you know sapConfig is located in.
Ex:
function Foo() {
var self = this;
this.test = "I am test";
var bar = function(){
return function(){
console.log(this.test); //outputs undefined (because this refers to the current function scope)
console.log(self.test); //outputs "I am test";
}
}
}
Here is your first code snippit with my example implemented:
"use strict";
var mongojs = require('mongojs'); // MongoDB API wrapper
module.exports = function(regKey) {
var self = this;
this.regKey = regKey;
this.sapConfig = null;
this.get_config = function() {
// Read SAP connection information from our MONGO db
var db = mongojs('mongodb://localhost/MIM', ['Configurations', 'Registrations']);
db.Registrations.find({ key: this.regKey }, function(err1, registration) {
console.log('Reg.find()');
console.log(registration[0]);
db.Configurations.find({ type: registration[0].type }, function(err2, config) {
console.log('Config.find()');
console.log('config=' + config[0].user);
self.sapConfig = 'test';
});
});
}
this.get_result = function() {
return self.sapConfig;
}
}
For your second snippet. You are trying to return a value from within your nested callback. Since nested functions are asyncronous, you cannot do that.
Here is how I like to return values from nested callbacks:
Ex2:
//Function example
var functionWithNested = function(done) {
//Notice the done param.
// It is going to be a function that takes the finished data once all our nested functions are done.
function() {
//Do things
function() {
//do more things
done("resultHere"); //finished. pass back the result.
}();//end of 2nd nested function
}(); //end of 1st nested function
};
//Calling the function
functionWithNested(function(result) {
//Callback
console.log(result); //resultHere
})
Here is your code using that example:
"use strict";
var mongojs = require('mongojs'); // MongoDB API wrapper
module.exports = function(regKey, done) {
// Read SAP connection information from our MONGO db
var db = mongojs('mongodb://localhost/MIM', ['Configurations', 'Registrations']);
db.Registrations.find({ key: regKey }, function(err1, registration) {
console.log('Reg.find()');
console.log(registration[0]);
db.Configurations.find({ type: registration[0].type }, function(err2, config) {
console.log('Config.find()');
console.log('config=' + config[0].user);
done(config[0].user);
});
});
}
//Then wherever you call the above function use this format
// if config is the name of the function export above...
new Config().(regKey, function(result){
console.log(result); //config[0].user value
})
Lots and lots of code, but I hope you were able to follow it. Let me know if you have any more questions! Cheers.
In node.js I have this scenario:
main.js
module.exports = {
dbHandler: {}
}
const DB_CONNECT = require('dbConnect.js');
const CHILD_MODULE = require('childModule.js');
module.exports.dbHandler = DB_CONNECT.connectDB(); // establishes the connection to the sqlite3 db
// ... give some time to module.exports.dbHandler to be loaded. (lab testing)
CHILD_MODULE.queryDB(); // <----- error occurs
childModule.js
var db = module.parent.exports.dbHandler;
//issue is here. Even after the parent have set dbHandler, this still empty {}.
module.exports.queryDB = function(){
db.all('SELECT * from mytable', (err, rows) => { // callback
console.log(rows);
}
Since DB_CONNECT.connectDB() is async, I give it a while (lab test) to load the database and updating module.exports.dbHandler before calling CHILD_MODULE.queryDB()
the error occurs when db.all is called.
TypeError: db.all is not a function
db still an empty object {}.
What is wrong in this code? How do I make the child's db to access the parent's module.exports.dbHandler ?
First of all, I will not fix your problem directly. I will try to explain my comment in above.
I have had a similar scenario in one of my projects. But I have used MongoDB. My db model looks like this:
var MongoClient = require('mongodb').MongoClient
var url = process.env.MONGO_URI
var collection = 'shortlinks'
var state = {
db: null
}
exports.connect = function (done) {
if (state.db) return done()
MongoClient.connect(url, function (err, db) {
if (err) return done(err)
state.db = db
done()
})
}
exports.get = function () {
return state.db
}
...
and some other methods
And I have accessed this module from different places for the same database connection with this line:
var db = require('../models/db')
I can access the same db instance with getter method and other methods as well.
For example, I have the code below:
var db = require('./_mongo.js');
module.exports = {
check: function (cb) {
var content = {};
content.collection = 'counters';
content.query = {_id: 'ping'};
content.columns = {};
db.read(content, function(err, result){
if (err) {
cb(-1);
}
else {
cb(0);
}
});
}
};
How do I write a unit test for the 'check' function, without actually accessing the database, while at the same time checking if I am able to code the correct 'content' variable being passed to the read method?
You can mock an entire module with a mock framework, like sinon.js:
var db = sinon.mock(require('_mongo.js'))
I would not recommend to mock database access, it could require you to code all possible responses...
It would be best if you would hide the database access behind an abstracted service layer and mock that layer.
For example, you can create a database access layer in this way:
var db = require('./_mongo.js');
module.exports = {
//this is a mockable method
getCounter: function (id, callback) {
var content = {};
content.collection = 'counters';
content.query = {_id: id};
content.columns = {};
db.read(content, callback);
}
};
//and then using it
module.exports = {
check: function (cb) {
//access the actual method or the mock
da.getCounter('ping', function(err, result){
if (err) {
cb(-1);
}
else {
cb(0);
}
});
}
};
test-studio provides mechanisms for stubbing out module dependencies. It also supports things like executing individual or groups of tests and stepping node-inspector into individual tests.
Read more about it here.
how do i share the db object returned from when i call db.open or db.connect across the entire app?
i have a dbconnect.js module as follows :
var mongodb = require('mongodb');
var global_db = '';
// Define options. Note poolSize.
var serverOptions = {
'auto_reconnect': true,
'poolSize': 5
};
// Now create the server, passing our options.
var serv = new mongodb.Server('localhost', 27017, serverOptions);
// At this point, there is no connection made to the server.
// Create a handle to the Mongo database called 'myDB'.
var dbManager = new mongodb.Db('myDB', serv);
// NOW we initialize ALL 5 connections:
dbManager.open(function (error, db) {
// Do something with the connection.
global_db = db;
// Make sure to call db.close() when ALL connections need
// to be shut down.
db.close();
});
function getConnection()
{
return global_db;
}
exports.getConnection = getConnection;
and i am using this dbconnect.js in my app.js as:
var http = require('http');
var db = require('./dbconnect').getConnection();
var collection = db.collection('testcollection');
console.log(db);
console.log(collection);
var server = http.createServer();
server.on('request',route);
server.listen(8000,'127.0.0.1');
console.log('Server running at http://127.0.0.1:8000');
function route(request,response)
{
var url = request.url;
var doc = {};
doc[url] = 'ok';
collection.insert(doc,{w:1},function(err,result)
{
if(err) console.log(err);
else console.log(result);
});
}
in the console, the db and collection variable show empty values, i also tried removing the db.close() call in dbconnect.js but to no use, however the insertion works when i place it inside dbconnect.js file in the dbManager.open function, how do i do this?or any similar alternatives?
You can't do that, because dbManager.open( is async method, but you trying to get data from module synchronously.
Try this:
In dbconnect.js
var on_db_ready = null;
module.exports = {
db_ready:function(db_ready_callback){
on_db_ready = db_ready_callback;
//here we call callback if already have db
if (global_db) on_db_ready(global_db);
},
getConnection:getConnection
};
dbManager.open(function (error, db) {
if (on_db_ready) on_db_ready(db);
global_db= db;
})
in app.js:
var db = require('./dbconnect').db_ready(function(db){
//Here i have my database
//or can use getConnection method
});
this is not very beautiful way, but, I hope, explain your mistake
I'm looking for a package (or pattern) to handle events from mongodb so I can avoid nested callbacks and keep mongodb logic out of my request handlers.
Right now I've got code that looks like this:
start-express.js (server)
var express = require('express');
var Resource = require('express-resource');
var app = express.createServer();
// create express-resource handler which essentially does app.get('things', ...)
var things = app.resource('things', require('./things.js'));
app.listen(port);
things.js (express-resource request handler)
require('./things-provider');
// handle request 'http://example.com/things'
exports.index = function(request, response) {
sendThings(db, response);
};
things-provider.js (handles mongodb queries)
var mongodb = require('mongodb')
// create database connection
var server = new mongodb.Server(host, port, {auto_reconnect: true});
var db = new mongodb.Db(dbName, server);
db.open(function (err, db) {
if (err) { }
// auto_reconnect will reopen connection when needed
});
function sendThings(db, response) {
db.collection('things', function(err, collection) {
collection.find(function(err, cursor) {
cursor.toArray(function(err, things) {
response.send(things);
});
});
});
}
module.exports.sendThings = sendThings;
I'd like to avoid passing my http response object to my database handler or (worse) handling my db request in my http response handler.
I recently realized that what I want to do is create an event handler that registers an http request/response and waits for a response (event) from database before processing and sending the http response.
That sounds like a lot of duplication of what node.js already does though. Is there an existing framework that handles this use case?
Here's the solution I've come up with.
I used mongojs which greatly simplifies the mongodb interface --at the cost of flexibility in configuration-- but it hides the nested callbacks the mongodb driver requires. It also makes the syntax much more like the mongo client.
I then wrap the HTTP Response object in a closure and pass this closure to the mongodb query method in a callback.
var MongoProvider = require('./MongoProvider');
MongoProvider.setCollection('things');
exports.index = function(request, response){
function sendResponse(err, data) {
if (err) {
response.send(500, err);
}
response.send(data);
};
MongoProvider.fetchAll(things, sendResponse);
};
It is still essentially just passing the response object to the database provider, but by wrapping it in a closure that knows how to handle the response, it keeps that logic out of my database module.
A slight improvement is to use a function to create a response handler closure outside my request handler:
function makeSendResponse(response){
return function sendResponse(err, data) {
if (err) {
console.warn(err);
response.send(500, {error: err});
return;
}
response.send(data);
};
}
So now my request handler just looks like this:
exports.index = function(request, response) {
response.send(makeSendResponse(response));
}
And my MongoProvider looks like this:
var mongojs = require('mongojs');
MongoProvider = function(config) {
this.configure(config);
this.db = mongojs.connect(this.url, this.collections);
}
MongoProvider.prototype.configure = function(config) {
this.url = config.host + "/" + config.name;
this.collections = config.collections;
}
MongoProvider.prototype.connect = function(url, collections) {
return mongojs.connect(this.url, this.collections);
}
MongoProvider.prototype.fetchAll = function fetchAll(collection, callback) {
this.db(collection).find(callback);
}
MongoProvider.prototype.fetchById = function fetchById(id, collection, callback) {
var objectId = collection.db.bson_serializer.ObjectID.createFromHexString(id.toString());
this.db(collection).findOne({ "_id": objectId }, callback);
}
MongoProvider.prototype.fetchMatches = function fetchMatches(json, collection, callback) {
this.db(collection).find(Json.parse(json), callback);
}
module.exports = MongoProvider;
I can also extend MongoProvider for specific collections to simplify the API and do additional validation:
ThingsProvider = function(config) {
this.collection = 'things';
this.mongoProvider = new MongoProvider(config);
things = mongoProvider.db.collection('things');
}
ThingsProvider.prototype.fetchAll = function(callback) {
things.fetchAll(callback);
}
//etc...
module.exports = ThingsProvider;
Well, first off I find Mongoose somewhat easier to use in a well-structured app than straight mongo. So that might help you.
Second, I think what you're trying to do could easily be accomplished through middleware (app level or route level), since you're using express already. Alternatively, parameter-filtering, if your query will vary based on params. A pattern I've seen on the last looks like this:
var User = mongoose.model("user'); // assumes your schema is previously defined
app.param('user_id', function(req,res,next, id){
User.find(id, function(err,user){
if(err) next(err);
else {
req.user = user;
next();
}
});
});
It still has some nesting, but not nearly so bad as your example, much more manageable. Then, let's say you have a '/profile' endpoint, you can just do:
app.get('/profile/:user_id', function(req,res){ res.render('profile', req.user); }