mocking MongoDB with proxyquire - javascript

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);
}
};

Related

Unit Testing BelongsToMany relations in Sequelize using Jasmine

I'm building a simple blog style application for practice using Node and Sequelize (with the Yeoman angular-fullstack generator). I'm trying to write out some tests for my article model, but I'm unable to figure out the right way to structure my tests. The articles and users are connected by a two-way belongsToMany relationship.
I'm able to verify that the setter and getter methods exist for my objects, but am unable to verify that they work. The code I'm trying to get to work is the code commented out at the end of the file. I'd appreciate any help in getting that unit test to work correctly, thanks!
'use strict';
/* globals describe, expect, it, beforeEach, afterEach */
var app = require('../..');
import request from 'supertest';
import {User, Article} from '../../sqldb';
var user = [];
var article = [];
var genArticles = function(i) {
for(var i = 0; i < 3; i++) {
article[i] = genArticle(i);
}
return article;
}
var genUsers = function(i) {
for(var i = 0; i < 3; i++) {
user[i] = genUser(i);
}
return user;
}
var genUser = function(i) {
return User.build({
provider: 'local',
name: `Fake User ${i}`,
email: `test${i}#example.com`,
password: 'password'
});
};
var genArticle = function(i) {
return Article.build({
title: `Fake Article ${i}`,
body: `test${i}`
});
};
describe("Article-User relations", function() {
before(function() {
return User.sync().then(function() {
return User.destroy({ where: {} }).then(function() {
return Article.sync().then(function() {
return Article.destroy({ where: {} }).then(function() {
});
});
});
});
});
beforeEach(function(done) {
genArticles();
genUsers();
done();
});
afterEach(function() {
return User.sync().then(function() {
return User.destroy({ where: {} }).then(function() {
return Article.sync().then(function() {
return Article.destroy({ where: {} }).then(function() {
});
});
});
});
});
it('should begin with no data', function() {
expect(User.findAll()).to
.eventually.have.length(0);
return expect(Article.findAll()).to
.eventually.have.length(0);
});
describe("Check methods exist and", function(done) {
before(function(done) {
article.forEach((a) => {a.save()});
user.forEach((a) => {a.save()});
done();
});
it("should have setter methods", function() {
expect(typeof article[0].setUsers).to.equal("function");
expect(typeof user[0].setArticles).to.equal("function");
});
it("should have getter methods", function() {
expect(typeof article[0].getUsers).to.equal("function");
expect(typeof user[0].getArticles).to.equal("function");
});
/* THIS IS THE TEST THAT ISN'T WORKING */
/*
it("setter and getter methods for article objects should work", function() {
return article[0].setUsers(user).then(() => {
return article[0].hasUsers(user).then( result => {
expect(result).should.eventually.equal.true;
})
}).catch( e => { console.log(e) });
//expect(typeof user[0].setArticles).to.equal("function");
});
*/
});
});

How to stub a dynamic object method using Sinon.js?

I have following module.
var Sendcloud = require('sendcloud');
var sc = new Sendcloud("key1","key2","key3");
var service = {};
service.restorePassword = function (params, cb) {
if (!params.to || !params.name || !params.token) {
throw "Miss params"
}
var defaultTemplate = adminBaseUrl + "reset/token/" + params.token;
var subject = params.subject || "Letter";
var template = params.template || defaultTemplate;
// Send email
sc.send(params.to, subject, template).then(function (info) {
console.log(info)
if (info.message === "success") {
return cb(null, "success");
} else {
return cb("failure", null);
}
});
};
module.exports = service;
I experience problems stubbing sc.send method. How to correctly cover this point using sinon.js? Or maybe I need to replace sendcloud module?
You need to use proxyquire module or rewire module.
Here an example of using proxyquire
var proxyquire = require('proxyquire');
var sinon = require('sinon');
var Sendcloud = require('sendcloud');
require('sinon-as-promised');
describe('service', function() {
var service;
var sc;
beforeEach(function() {
delete require.cache['sendcloud'];
sc = sinon.createStubInstance(Sendcloud);
service = proxyquire('./service', {
'sendcloud': sinon.stub().returns(sc)
});
});
it('#restorePassword', function(done) {
sc.send.resolves({});
var obj = {
to: 'to',
name: 'name',
token: 'token'
};
service.restorePassword(obj, function() {
console.log(sc.send.args[0]);
done();
});
});
});

Forcing Node.js modules to follow an interface

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);
}

How to pass scope to callback functions in nodejs?

Im using nodejs and the xml2js module. I'm reading an XML file and try to emit an event after the xml is converted to an json object. My code looks like this :
var fs = require('fs'),
util = require('util'),
events = require('events'),
xml2js = require('xml2js');
var CIRCUITMODELSFILENAME = "ControlCircuitModels.xml";
var CIRCUITPARTMODELSFILENAME = "ControlCircuitParts.xml";
var circuitModels, circuitPartModels;
function ModelController() {
if (false === (this instanceof ModelController)) {
return new ModelController();
}
events.EventEmitter.call(this);
};
util.inherits(ModelController, events.EventEmitter);
ModelController.prototype.load = function (baseDir) {
var parser = new xml2js.Parser({
normalize: true,
trim: true,
explicitArray: false
});
fs.readFile(baseDir + "/" + CIRCUITMODELSFILENAME, function (err, data) {
parser.parseString(data, function (err, result) {
circuitModels = result;
console.log('circuit models loaded');
parser.reset();
fs.readFile(baseDir + "/" + CIRCUITPARTMODELSFILENAME, function (err, data) {
parser.parseString(data, function (err, result) {
circuitPartModels = result;
console.log('circuit part models loaded');
moduleReady = true;
this.emit("modelsloaded", null);
});
});
});
});
};
// public interface
exports.ModelController = ModelController;
Problem is that the scope when emitting the event is lost.
this.emit("modelsloaded", null);
this hasn't inherited the emit from EventEmitter.
How can I pass the scope to the parser.parseString function?
Thanks
Chris
Not sure if this is the best solution, bis this works (doesn't look straight foreward):
fs.readFile(baseDir + "/" + CIRCUITMODELSFILENAME, function (err, data) {
parser.parseString(data, function (err,result) {
circuitModels = result;
parser.reset();
fs.readFile(baseDir + "/" + CIRCUITPARTMODELSFILENAME, function (err, data) {
circuitPartModels = result;
console.log('circuit models loaded');
parser.parseString(data, function (err, result) {
console.log('circuit part models loaded');
this.emit("modelsloaded", null);
moduleReady = true;
circuitPartModels = result;
}.bind(this));
}.bind(this));
}.bind(this));
}.bind(this));

Synchronous function calls for nodejs mongodb driver

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();
});
});
});
}

Categories

Resources