I have a simple generic DB interface and want to force my DB adapters to follow that interface. So, both db.js and fake_mongo_adapter.js should have the same functions' names and params, like this:
db.init(client, options, callback)
db.get(client, query, callback)
Now I'm doing it with Underscore's extend function and throwing exceptions if there is no needed function.
Is it ok?
Is there any other approaches to force several Node.js modules to follow the one contract?
server.js, Entry point
var _ = require('underscore');
var mongoose = require('mongoose'); // Adaptee
var db = require('./db');
var fake_mongodb_adapter_raw = require('./fake_mongodb_adapter');
var fake_mongodb_adapter = _.extend({}, db);
fake_mongodb_adapter = _.extend(fake_mongodb_adapter, fake_mongodb_adapter_raw);
mongoose.connect('connection string');
fake_mongodb_adapter.init(mongoose, options, function run_app(err, person_model) {
fake_mongodb_adapter.get(person_model, { _id: '1234' }, function (err, found_person) {
console.log('found person: ' + JSON.stringify(found_person));
});
});
db.js, Generic DB interface used by the client, it shouldn't be changed.
module.exports = {
init: init,
get: get
};
function init(client, options, callback) {
throw new Error('Function "init" not implemented');
}
function get(client, query, callback) {
throw new Error('Function "get" not implemented');
}
fake_mongodb_adapter.js
module.exports = {
init: init,
get: get
};
function init(client, options, callback) {
console.log('initialized fake MongoDB adapter');
return callback(null);
}
function get(client, query, callback) {
var person = { id: '1234', name: 'Mary' };
return callback(null, person);
}
Related
I've written a test against my itemService function getItemsForUser to assert on the Items returned from a Mongoose find call. However, my test is currently passing regardless of the user object passed in because it mocks out the whole callback passed to exec.
My question is, how can I mock out only the Items returned by the find and populate but not skip the rest of the logic, so that the test does actually call doUsersContainUser?
itemService.js
const Item = require('../models/item');
exports.getItemsForUser= function(user, cb)
{
Item.find({}).populate('users').exec(function (err, itemList)
{
if (err) { throw next(err) }
else {
cb(null, itemList.filter(i=> doUsersContainUser(i, user._id));
}
});
};
function doUsersContainUser(item, userId) {
return item.users.some(u => hasValue(u, 'user', userId))
}
item.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ItemSchema = new Schema({
users: [{ type: Schema.Types.ObjectId, ref: 'User' }]
});
module.exports = mongoose.model('Item', ItemSchema );
itemServiceTest.js
it('should return all items for user', function () {
const allItems = itemsData.allItemsPopulated();
const user = { id: 10001 };
sinon.mock(Item).expects('find')
.chain('populate', 'users')
.chain('exec')
.returns(null, allItems);
itemService.getItemsForUser(user, function(err, result) {
assert.strictEqual(result, allItems);
});
});
I am using sinon-mongoose for the chained mongoose calls.
I'm trying to get a result from Sql query.
I have the following class which should execute the Sql connection and query:
'use strict';
//db.js
const sql = require('mssql');
require('dotenv').config();
var utils = function(){
var config = {
server: 'sql01',
database: 'db123',
options: {
instanceName: 'in1',
encrypt: true
}
};
module.exports = {
/** Define sql queries here */
GetEmpName(id) {
let my_query = `Select filed1 FROM db123 WHERE id='${id}'`;
sql.connect(config).then(function () {
new sql.Request()
.query(my_query).then(function (recordset) {}).catch(function (err) {
console.log(err);
});
});
}
};
};
And that's the main page:
'use strict;'
let HomePage = require('../page/home_page.js');
let utilsPage = require('../utils/utils.js');
describe("login to website",function(){
let employeeId;
let employeeBday;
let home = new HomePage();
//let utils = new utilsPage();
it('get an employee's Id', function (done) {
utilsPage.GetEmpName('100001387');
done();
})
});
I'm getting an error says: utilsPage is not a constructor.
What am I doing wrong?
In Node.js there are two ways of exporting and accessing function variables. One is by creating a global function and putting all your functions, variables into that and export that module as a whole by using module.exports.
Another way is just exporting the function by exports and then accessing that in your specific file. So in your case you could do it in either of the below 2 ways-
1st Approach: Using your Utils function globally -
'use strict';
//db.js
const sql = require('mssql');
require('dotenv').config();
var utils = function(){
this.config = {
server: 'sql01',
database: 'db123',
options: {
instanceName: 'in1',
encrypt: true
}
};
/** Define sql queries here */
this.getEmpName = function(id) {
let my_query = `Select filed1 FROM db123 WHERE id='${id}'`;
sql.connect(config).then(function () {
new sql.Request()
.query(my_query).then(function (recordset) {}).catch(function (err) {
console.log(err);
});
});
}
};
module.exports = new utils();
You could use it the same way as you have used -
'use strict;'
let HomePage = require('../page/home_page.js');
let utilsPage = require('../utils/utils.js');
describe("login to website",function(){
let employeeId;
let employeeBday;
let home = new HomePage();
it('get an employee's Id', function (done) {
utilsPage.getEmpName('100001387');
done();
})
});
2nd Approach: Exporting only the getEmpName function -
'use strict';
//db.js
const sql = require('mssql');
require('dotenv').config();
var config = {
server: 'sql01',
database: 'db123',
options: {
instanceName: 'in1',
encrypt: true
}
};
/** Define sql queries here */
exports.getEmpName = function(id) {
let my_query = `Select filed1 FROM db123 WHERE id='${id}'`;
sql.connect(config).then(function () {
new sql.Request()
.query(my_query).then(function (recordset) {}).catch(function (err) {
console.log(err);
});
});
}
You would use it the same as well:
'use strict;'
let HomePage = require('../page/home_page.js');
let utilsPage = require('../utils/utils.js');
describe("login to website",function(){
let employeeId;
let employeeBday;
let home = new HomePage();
it('get an employee's Id', function (done) {
utilsPage.getEmpName('100001387');
done();
})
In your utils.js file you shouldn't wrap your code in the utils function
'use strict';
//db.js
const sql = require('mssql');
require('dotenv').config();
var config = {
server: 'sql01',
database: 'db123',
options: {
instanceName: 'in1',
encrypt: true
}
};
module.exports = {
/** Define sql queries here */
GetEmpName(id) {
let my_query = `Select filed1 FROM db123 WHERE id='${id}'`;
sql.connect(config).then(function () {
new sql.Request()
.query(my_query).then(function (recordset) { }).catch(function (err) {
console.log(err);
});
});
}
};
In your main page file you can access your exported module like this
utilsPage.GetEmpName('100001387')
There's no need to call new utilsPage(). In order to use the keyword new you must either export a class or a constructor function.
I'd like to mock MongoDB dependency with proxyquire
by doing this in my test:
var proxyquire = require('proxyquire');
var controller = path.resolve('.path/to/controller/file.js');
in the before each statement:
mocked_mongoose = {
isMocked: true,
model: function(name, schema, collection, skipInit) {
return {
find: function(conditions, projection, options, callback) {
console.log('callback find');
return callback();
},
save: function(options, fn) {
console.log('callback save');
return callback();
},
findOne: function(conditions, projection, options, callback) {
console.log('callback find one');
var model = mongoose.model(name);
var fakeModel = fakery.fake(model);
return callback(null, fakemodel);
}
}
}
};
proxyquire(controller, {
'mongoose': mocked_mongoose
});
and when I go to the controller and do
console.log(mongoose.isMocked) I got undefined and if I print mongoose.model.toString() seems like the mongoose methods aren't overridden.
I followed up this article and tried to implement the same logic, but I'm not getting the same results.
any help will be appreciated,
thanks!
I ended up by changing the way I was defining my mocked mongoose object, to match exactly the scenarios I wanted to test in mt case:
first model instantiation
var Model = mongoose.model('SchemaDef');
var other = Model({
_id:'someId'
name'Some other fields'
});
model search:
Model.findOne(query, callback);
this the version how it works:
'use strict';
var factory = require('factory-girl');
var mongoose = require('mongoose');
function viewModel(name){
function constructor(obj){
if(obj){
obj.find = constructor.find;
obj.save = constructor.save;
obj.findOne = constructor.findOne;
}
return obj;
};
constructor.isMocked = true;
constructor.find = function(query, callback){
factory.build(name, function(err, mockedModel){
return callback(null,[mockedModel]);
});
};
constructor.save = function(callback){
factory.build(name, function(err, mockedModel){
return callback(null,[mockedModel]);
});
};
constructor.findOne=function(query,callback){
factory.build(name, function(err, mockedModel){
return callback(null,mockedModel);
});
};
return constructor;
};
module.exports = {
model:function(name){
factory.define(name, mongoose.model(name));
return viewModel(name);
}
};
I have two simple test but one of the test passed but not the other one because of the Schema getting compiled again.
OverwriteModelError: Cannot overwrite CheckStaging model once
compiled.
Here's my one test that passed because it's being run first.
var mongoose = require('mongoose'),
StagingManager = require('../lib/staging_manager'),
expect = require('expect.js');
describe('Staging manager', function() {
var StagingModel;
beforeEach(function(done) {
mongoose.connect('mongodb://localhost/BTest');
StagingModel = new StagingManager(mongoose).getStaging();
done();
});
describe('find one', function() {
it('should insert to database', function(done) {
// Do some test which works fine
});
});
afterEach(function (done) {
mongoose.connection.db.dropDatabase(function () {
mongoose.connection.close(function () {
done();
});
});
});
});
And here's the test that failed
var StagingUtil = require('../lib/staging_util'),
StagingManager = require('../lib/staging_manager'),
mongoose = require('mongoose');
describe('Staging Util', function() {
var stagingUtil, StagingModel;
beforeEach(function(done) {
mongoose.connect('mongodb://localhost/DBTest');
StagingModel = new StagingManager(mongoose).getStaging();
stagingUtil = new StagingUtil(StagingModel);
done();
});
describe('message contains staging', function() {
it('should replace old user with new user', function(done) {
// Do some testing
});
});
afterEach(function (done) {
mongoose.connection.db.dropDatabase(function () {
mongoose.connection.close(function () {
done();
});
});
});
});
And here's my staging manager
var Staging = function(mongoose) {
this.mongoose = mongoose;
};
Staging.prototype.getStaging = function() {
return this.mongoose.model('CheckStaging', {
user: String,
createdAt: { type: Date, default: Date.now }
});
};
module.exports = Staging;
mongoose.model registers a model with Mongoose, so you should only be calling that once rather than each time you call getStaging. Try something like this for your staging model instead:
var mongoose = require('mongoose');
var StagingModel = new mongoose.Schema({
user: String,
createdAt: { type: Date, default: Date.now }
});
mongoose.model('CheckStaging', StagingModel);
Then in your consuming code, use
var mongoose = require('mongoose');
require('../lib/staging_manager');
var StagingModel = mongoose.model('CheckStaging');
The require will only execute once, so the model should only be registered with mongoose once.
As an aside, for unit testing, mockgoose is an excellent mocking library to mock out mongoose - worth investigating!
I have an open source project that deals with mongodb database. I am trying to make a function that queries the database to check if entry exists.
The problem is when if_exists() returning true or false it returns undefined since the mongodb driver function is asynchronous. The file is Query.js and I have tried the solution here to workaround the problem What is the right way to make a synchronous MongoDB query in Node.js? but still I get an undefined result with the get method.
What is the best way to make this work?
The output from the unit tests is as the following:
running unit tests...
add query test
exists tests:
get: undefined
{}
should be true: undefined
get: undefined
{}
should be false:undefined
Captains Logs listening on port 3000
Captains_Logs v0.5.0-21
[ { name: 'rhcp', _id: 50cbdcbe9c3cf97203000002 } ]
[ { name: 'os', _id: 50cbdcbe9c3cf97203000001 } ]
You can browse the whole codes at WeaponXI/cplog
Or for a quick look the query.js code is:
var DB = require('../../lib/db.js').DB;
function methods() {
//query object
var Q = {};
//will act as our private variables to workaround asynchronous functions.
//will delete non-required ones when done -- we don't have to, but just for continuity.
exports.privates = {};
//add tag to collection
Q.add = function(tag) {
if (typeof tag === "string") {
//maybe we are adding a tag by name
var obj = {
name: tag
};
} else if (typeof tag === "object" && tag.name) {
//maybe the tag object was specified, and tag's name was provided
var obj = tag;
}
require('mongodb').connect(DB.mongo_url, function(err, db) {
db.collection('tags', function(err, coll) {
coll.insert(obj, {
safe: true
}, function(err, result) {
console.log(result);
});
});
});
}
var callback = {
_set: function(key, val) {
exports.privates[key] = val;
//console.log(JSON.stringify(privates));
},
_get: function(key) {
console.log("get: "+exports.privates.key);
console.log(JSON.stringify(exports.privates));
return exports.privates[key];
},
_unset: function(key) {
delete privates[key];
}
}
var if_exists = function(query, where, callback) {
require('mongodb').connect(DB.mongo_url, function(err, db) {
db.collection(where, function(err, coll) {
coll.findOne(query, function(e, r) {
//console.log(r);
if (r === null) {
callback._set("does_exist", false);
} else {
callback._set("does_exist", true);
}
});
});
});
var result = callback._get("does_exist");
// delete privates.does_exist;
return result;
}
Q.if_exists = function(query, where) {
if_exists(query, where, callback);
}
return Q;
}
var query = exports.query = methods();
function unit_test_add() {
console.log("add query test");
query.add("os");
query.add({
name: "rhcp"
});
}
function unit_test_if_exists() {
console.log("exists tests:");
console.log("should be true: " + query.if_exists({
name: "os"
}, "tags"));
console.log("should be false:" + query.if_exists({
name: "ossuruk"
}, "tags"));
}
function unit_tests() {
console.log("running unit tests...");
unit_test_add();
unit_test_if_exists();
}
unit_tests();
Solution:
Query.js Query.test.js Gists
Thanks JohnnyHK!
You cannot use an asynchronous result as the return value from a function. It's that simple. You have to deliver the asynchronous result to the caller via a callback that is provided as a parameter to the function (or use futures/promises and effectively defer that step, but that's more involved).
if_exists should look like this instead:
var if_exists = function(query, where, callback) {
require('mongodb').connect(DB.mongo_url, function(err, db) {
db.collection(where, function(err, coll) {
coll.findOne(query, function(e, r) {
//console.log(r);
if (r === null) {
callback(e, false);
} else {
callback(e, true);
}
// You should either close db here or connect during start up
// and leave it open.
db.close();
});
});
});
}