mocha test function with mongoose call - node.js

Having some problems unit testing the below code, I'm unsure whether it is possible or not due to the way it is coded.
storeModel.js
var storeSchema = new Schema({
storeId : { type: String, index: true},
storeName : String
});
var model = mongoose.model('store', storeSchema);
var findStoresById = function(ids, callback) {
model.find({ storeId: { $in: ids }}, function (err, result) {
if (err) callback(err);
callback(err, result);
});
};
return {
findStoresById: findStoresById,
schema: storeSchema,
model: model
};}();
Which i test like so..
it('will call "findStoresById" and return matched values [storeId: 1111] ', function (done) {
storeModel.findStoresById(['1111'], function(err, store) {
assert.equal(store[0].storeId, '1111');
assert.equal(store[0].storeName, 'StoreName');
assert.equal(err, null);
done();
});
});
However the problem when i implement the following code within a separate function:
get: function (req, res) {
if (req.query.storeIds) {
var ids = req.query.storeIds.split(',');
storeModel.findStoresById(ids, function(err, stores) {
if (err) {
return res.send(err);
}
if (_.isEmpty(stores)) {
var error = {
message: "No Results",
errorKey: "XXXX"
}
return res.status(404).json(error);
}
return res.json(stores);
}); ...
How can i unit test this, i dont want to mock it as there is functionality in the "findStoreById" that needs testing, or is a refactor needed? suggestions?

I'd contest that you actually should be stubbing findStoreById because in not doing so get cannot strictly be unit tested, in that it's not isolated and could fail through no fault of its own. Seeing as the functionality you'd want to test lies within the callback of findStoreById and not the method itself, we can happily stub it and use the yields method of sinon to invoke its callback accordingly.
Note that, if you're testing routes, it's preferable to use supertest else you'll have a lot of mocking of request and response methods on your hands. Therefore, for example:
var request = require('supertest');
var express = require('express');
// stub database method
sinon.stub(storeModel, 'findStoresById');
// create a test app/route to which we direct test requests
var app = express();
app.get('/', myRouteFunction);
it('sends a 404 error when no stores are found', function(done) {
// use the second argument of `yields` to pass a result to the callback
storeModel.findStoresById.yields(null, []);
request(app).get('/').expect(404, done);
});
it('responds with any stores found', function(done) {
// pass an array of found stores to the callback
storeModel.findStoresById.yields(null, [{_id: 1}]);
request(app).get('/').end(function(err, res) {
if(err) return done(err);
assert.deepEqual(res.body, [{_id: 1}]);
done();
});
});

If what you want is test static's and method's of certain Mongoose model, I would recommend you to use sinon and sinon-mongoose.
But first, some tips for your code
var storeSchema = new Schema({
storeId : { type: String, index: true},
storeName : String
});
// 1) If you will callback with the same 'err' and 'result', pass the callback directly
function findStoresById(ids, callback) {
// Instead of this...
this.find({ storeId: { $in: ids } }, function (err, result) {
if (err) callback(err);
callback(err, result);
});
// Use this... it's the same, but shorter
this.find({ storeId: { $in: ids } }, callback);
}
// 2) Declare a static method on your model, instead of export manually (see Monggose documentation for more info)
storeSchema.static('findStoresById', function (ids, callback) {
});
// 3) Create your model after the statics were declared, and use CamelCase
var model = mongoose.model('Store', storeSchema);
// 4) Export just your model
// If you want the model -> var Store = mongoose.model('Store')
// If you want the schema -> var schema = Store.schema
// If you want to call your method -> Store.findStoresById(...)
module.exports = model;
Then, to test the method findStoresById
var sinon = require('sinon');
require('sinon-mongoose');
var Store = mongoose.model('Store');
sinon.mock(Store)
.expects('find').withArgs({ storeId: { $in: ['id1', 'id2'] } })
.yields(null, 'SUCCESS!');
Store.findStoresById(['id1', 'id2'], function (err, res) {
assert(res, 'SUCCESS!');
});
You can find working (and simple) examples on the sinon-mongoose repo.

Related

How to mock events with sinon?

Here is my simple program where I'm trying to get user's data via ldapClient. I need to test it without internet connection so wondering how to mock that result.on events to return appropriate data.
var request = require('request');
var ldap = require('ldapjs');
....
var ldapClient = ldap.createClient(ldapConfig);
....
var MY_CLASS = {
getData: function (userId, cb) {
if (!ldapConfig) {
return cb(new Error('ldap is not configured'));
}
ldapClient.search('xxxx.com', { ldapConfig },
function (err, result) {
if (err) {
return cb(err);
}
result.on('searchEntry', function (entry) {
if (entry) {
return entry;
}
});
result.on('error', function (err) {
cb(err);
});
result.on('end', function () {
cb(null, 'END');
});
});
}
};
module.exports = MY_CLASS;
Looking for something (see below) but assume I need to use a spy. But how do I define it in that deep nested class?
before(()=>{
sinon
.stub(MY_CLASS.ldapClient, 'search')
.yields(???);
});
after(()=>{
MY_CLASS.ldapClient.search.restore();
});
If you are stubbing the library, and not exporting from the original class file, you will need to import instead of referencing it as a method/property on the class
Then you'll want to use callsArg from sinon to call the callback
var ldapClient = ldap.createClient(ldapConfig);
...
var ldapStub;
before(()=>{
ldapStub = sinon
.stub(ldapClient, 'search')
.callsArg(2);
});
after(()=>{
ldapStub.restore();
});
You can then include assertions on the stub (for example ldapStub.calledOnce should be true, etc)

nodejs- not able to save data returned by an asynchronous function

I'm getting undefined when I trying to print a value to console which is returned by an asynchronous function.
This is happening in my controller function (dumyController.js) which calls a function written in helper (DBHelper.js), for DRY approach, which
asynchronously fetches data from a model function in DBHelperModel.js
dumyController.js
var dbHelpers = require('../helpers/helpers');
exports.dumyControllerFunc = function (req, res) {
var result= dbHelpers.dumyHelperFunc(165);
console.log(result);
};
DBHelper.js
var dbHelp = require('../models/DBHelperModel');
module.exports = {
dumyHelperFunc: function (userId) {
dbHelp.fetchDataFromDB(userId, function (err, rows) {
var res;
if (err) {
return null;
}
else {
res.send(rows.member_code);
}
});
}
};
DBHelperModel.js
var db = require('../db');
var DBHelpers = {
fetchDataFromDB: function (userId, callback) {
var query = `SELECT member_code FROM members where id=?`;
db.query(query, userId, callback);
},
};
module.exports = DBHelpers;
db.js
var mysql = require('mysql');
var connection = mysql.createPool({
host: '127.0.01',
user: 'root',
password: '',
database: 'dumyDB'
});
module.exports = connection;
I know I'm not getting value because of the asynchronous nature of the function but can anyone tell me how to fetch the value with an architecture like given above. I'm new to nodeJS. Thanks!
I think you want a solution about the asynchronous in nodejs.
The promise,that is a universal and popular solution
Use a callback,That is a approach in nodejs's way,anyway,that is native nodejs's asynchronous solution .
Other asysn: Generate and async, that's es6 feature,and the latter is support by nodejs7+
The promise is most recommend,it has a good looks structure if has a asycn chain, The callback looks bad on that.
This is probably what you trying to achieve:
https://repl.it/MA0Z/0
var DBHelpers = {
fetchDataFromDB: function (userId, callback) {
callback(null, 'hello fetchDataFromDB');
},
};
var dbhelp = {
dumyHelperFunc: function (userId, callback) {
DBHelpers.fetchDataFromDB(userId, function (err, rows) {
if (err) {
callback('error bro');
} else {
callback(null, rows);
}
});
}
}
dbhelp.dumyHelperFunc(165, function (error, result) {
console.log(result);
});

add extra parameters using mongoose transform on aggregate

I am new to node.js. I am using mongoose, node.
var schema = new Schema({
name: String,
});
schema.options.toObject.transform = function (doc, ret, options) {
tmp = ret;
tmp['abcd'] = 'abcd';
return tmp;
}
module.exports = mongoose.model('modelName', schema);
//my route.js
modelName.find({}, function (err, docs) {
docs.forEach(function(doc) {
console.log(doc);
console.log("doc['abcd'] is available");
});
});
modelName.aggregate({"$match":{"_id":{"$ne":0}}} function (err, docs) {
docs.forEach(function(doc) {
console.log(doc);
console.log("doc['abcd'] is not available");
});
});
I am using above code to add extra parameters while rendering. But when i use aggregate, it is not working. Is there any way to add extra parameters while rendering even on aggregate also? Thanks in advance.

Node.js — mongoose static method example

I wish to define a method on a model UserModel such that I get the names of all the users that have userId < 10.
Following is my implementation:
// pseudo code
UserModel === {
userId : Number,
userName: String
}
UserSchema.statics.getUsersWithIdLessThan10 = function(){
var usersLessThan10 = []
this.find({userId : {$lt : 10}}, function(error, users){
users.forEach(function(user){
console.log(user.userName) // ... works fine
usersLessThan10.push(user.userName)
})
})
return usersLessThan10
}
I understand why this doesn't seem to work — async find API. But if that's the case, then whats the way to do it? This async stuff is kind of overwhelming.
Add callback and return the users in this callback as follows:
UserSchema.statics.getUsersWithIdLessThan10 = function(err, callback) {
var usersLessThan10 = []
this.find({userId : {$lt : 10}}, function(error, users){
users.forEach(function(user){
console.log(user.userName) // ... works fine
usersLessThan10.push(user.userName)
})
callback(error, usersLessThan10)
})
}
Then call usersLessThan10 with the callback:
... .usersLessThan10(function (err, users) {
if (err) {
// handle error
return;
}
console.log(users);
})
try this:
API code:
var UserApi = require('./UserSchema');
var callback = function(response){
console.log(response); // or res.send(200,response);
}
UserApi.getUsersWithIdLessThan10(callback);
UserSchema code:
UserSchema.getUsersWithIdLessThan10 = function(callback){
var usersLessThan10 = []
this.find({userId : {$lt : 10}}, function(error, users){
if (error)
{ callback(error)}
else{
users.forEach(function(user){
console.log(user.userName) // ... works fine
usersLessThan10.push(user.userName);
//TODO: check here if it's the last iteration
callback(usersLessThan10);
})
}
})
}

Partial update of a subdocument with nodejs/mongoose

Is it possible to set multiple properties on a (sub)document in one go with Mongoose? An example of what I'm trying to do:
Let's say I have this schema:
var subSchema = new Schema({
someField: String,
someOtherField: String
});
var parentSchema = new Schema({
fieldOne: String,
subDocs: [subSchema]
})
Then I would like to do:
exports.updateMyDocument = function(req, res) {
var parentDoc = req.parentDoc; // The parent document. Set by parameter resolver.
var document = req.myDoc; // Sub document of parent. Set by parameter resolver.
var partialUpdate = req.body; // updated fields sent as json and parsed by body parser
// I know that the statement below doesn't work, it's just an example of what I would like to do.
// Updating only the fields supplied in "partialUpdate" on the document
document.update(partialUpdate);
parentDoc.save(function(err) {
if(err) {
res.send(500);
return;
}
res.send(204);
});
};
Normally, I could achieve this using the $set operator, but my problem is that document in this example is a subdocument (embedded schema) of parentDoc. So when I tried to do
Parent.update({_id: parentDoc._id, "subDocs._id": document._id},
{$set: {"subDocs.$" : partialUpdate}},
function(err, numAffected) {});
it replaced the subdocument instance identified by subDocs._id. Currently I have "solved" it by setting only fields manually, but I was hoping for a better way to do this.
Build up a $set object programmatically based on the fields of partialUpdate to update just those fields using dot notation:
var set = {};
for (var field in partialUpdate) {
set['subDocs.$.' + field] = partialUpdate[field];
}
Parent.update({_id: parentDoc._id, "subDocs._id": document._id},
{$set: set},
function(err, numAffected) {});
I've done different, in a REST application.
First, I have this route:
router.put('/:id/:resource/:resourceId', function(req, res, next) {
// this method is only for Array of resources.
updateSet(req.params.id, req.params.resource, req, res, next);
});
and the updateSet() method
function updateSet(id, resource, req, res, next) {
var data = req.body;
var resourceId = req.params.resourceId;
Collection.findById(id, function(err, collection) {
if (err) {
rest.response(req, res, err);
} else {
var subdoc = collection[resource].id(resourceId);
// set the data for each key
_.each(data, function(d, k) {
subdoc[k] = d;
});
collection.save(function (err, docs) {
rest.response(req, res, err, docs);
});
}
});
}
The brilliant part is mongoose will validate the data if you define the Schema for this subdocument. This code will be valid for any resource of the document that is an Array. I'm not showing all my data for simplicity, but is a good practice to check for this situations and handle the response error properly.
You can assign or extend embedded document.
Doc.findOne({ _id: docId })
.then(function (doc) {
if (null === doc) {
throw new Error('Document not found');
}
return doc.embeded.id(ObjectId(embeddedId));
})
.then(function(embeddedDoc) {
if (null === embeddedDoc) {
throw new Error('Embedded document not found');
}
Object.assign(embeddedDoc, updateData));
return embeddedDoc.parent().save();
})
.catch(function (err) {
//Do something
});
And in this case you should be shure that _id is not assigning.
I handled this in a slightly different manner without using the $set object. My approach is similar to Guilherme's but one difference is that I wrapped my method into the statics functionality so that it is easier to re-use throughout my application. Example below.
In CollectionSchema.js server model.
collectionSchema.statics.decrementsubdocScoreById = function decreasesubdoc (collectionId, subdocId, callback) {
this.findById(collectionId, function(err, collection) {
if (err) console.log("error finding collection");
else {
var subdoc = collection.subdocs.filter(function (subdoc) {
return subdoc._id.equals(subdocId);
})[0];
subdoc.score -= 1;
collection.save(callback);
}
});
};
In Server Controller
Collection.decrementsubdocScoreById(collectionId, subdocId, function (err, data) {
handleError(err);
doStuffWith(data);
});

Resources