How to unit test waterline model create method instance - node.js

I want to unit test a function in my sails.js application using Mocha(test framework) and Sinon(test double library). This is how the function under test looks like
function saveNotifications(notificationData, cb){
checkForDuplicates(notificationData, (err, isDuplicate) => {
if(isDuplicate) return updateNotification(notificationData, (err, updatedData)=> { cb(err, updatedData) })
// No duplicate exists, so save the notification in db
Notification.create(notification).exec((err, savedData) => {
return cb(err, savedData)
})
})
})
Although I was able to fake checkForDuplicates and add some tests for that. But when I wanted to stub(or fake) Notification.create(notification).exec method in order to check if create method was called once for particular test case, I was not able to figure out how to do that. Neither do I understand how to stub chained methods nor am I able to stub simple Notification.create object itself. I am assuming some gaps in my understanding of how to stub waterline method instances. Appreciate any help/advice.
What I have tried
I tried to do simpler things first i.e. stubbing create method
it('#saveNotifications({userId: "1", message: "Test message"}, cb)', (done) => {
sinon.stub(Notification, 'create');
saveNotifications({userId: "1", message: "Test message"}, (err, result) => {
try {
expect(err).to.not.throw;
expect(err).to.be.null;
expect(result).to.be.not.null;
sinon.assert.calledOnce(Notification.create);
sinon.restore();
done();
} catch(e){
sinon.restore();
done(e);
}
})
but this gives error
TypeError: Cannot stub non-existent property create

There are two issues you mentioned, let's sort them out one by one
Stub simple Notification.create object?
You have already written the right code for this but you are not referencing the correct model object. Instead of Notification object, use sails.models.notification i.e.
sinon.stub(sails.models.notification, 'create')
How to stub chained methods
Fake the returned value and use stub in the returned function i.e.
sinon.stub(sails.models.notification, 'create').callsFake(function(){
return {
exec: theStubbedChainFunction
}
})
var theStubbedChainFunction = sinon.stub().yields(null, { id: '1', message: 'canned response'})
// Then you can assert to test
sinon.assert.calledOnce(theStubbedChainFunction)
If you had more chained function (e.g. Notification.update(criteria).set(valuedToUpdate).fetch().exec(function(err, updatedRecords){...}), you could follow the same principal e.g.
sinon.stub(sails.models.notification, 'update').callsFake(function(){
return {
set: sinon.stub().callsFake(function(){
return {
fetch: sinon.stub().callsFake(function(){
exec: theStubbedChainFunction
})
}
})
}
})

Related

Node express multiple queries in one route that depend on each other

I am just about getting familiar with node and express and programming in general, but this is a more complex issue I am trying to solve. Please if you can provide with some best practice in this kind of scenario.
I am trying to run two queries to my database where the first one is dependent on the result of the first. Q1. Return a list of ids. Q2. Return id and coord for each of the ids. I want to respond with a json object that look something like this
[
{ id: 451, coords: 'POINT(12.5574 43.8351)' },
{ id: 56, coords: 'POINT(13.5574 44.8351)' }
]
Currently I cannot get it to work, I know there is probably several issues with my example code, but I have pretty much got stuck. Maybe I am overthinking this and make it harder than it is, or bad practice in general.
How can I run multiple queries where the second use the output from the first one and then build the correct object to respond with. Any pointers would be much appreciated.
router.get('/asset/:id', (req, res) => {
let latLngOfAssets = []
// get associated assets
function getConnectionsById() {
queries.getConnectionsById(req.params.id) // return list of objects
.then(data => {
if (data) {
data.forEach(function(element) {
getLatLngofAsset(element.til_poi_id) // for each id in list call function to get coordinate
});
} else {
throw new Error('No data returned');
}
console.log(latLngOfAssets) // What I want to respond with res.json(latlngofassets)
})
}
function getLatLngofAsset(id) {
queries.getPoilatlng(id) // Return geometry as text for asset with id
.then(data =>{
let newassetobj = {}
if (data) {
newassetobj["id"] = data.rows[0].id
newassetobj["coords"] = data.rows[0].st_astext
//console.log(newassetobj) // structure of each object { id: 451, coords: 'POINT(12.5574 43.8351)' }
latLngOfAssets.push(newassetobj) // making list of objects i want to respond with
} else {
throw new Error('Did not get any poi');
}
})
}
getConnectionsById()
.catch(err => { // {message: "Cannot read property 'then' of undefined", error: {…}}
console.error('Something went wrong', err);
});
});
You've done a good job separating out the two distinct sections of your code into separate functions - what you're missing is the ability to tie them together. This portion of your code is not doing what I think you are trying to accomplish:
data.forEach(function(element) {
getLatLngofAsset(element.til_poi_id)
});
Because getLatLngofAsset() is Promise-based* you need to use it like a Promise. You first need to make getLatLngofAsset return the Promise chain it creates. Then, it can be await-ed inside getConnectionsById using an async function:
function getConnectionsById() {
queries.getConnectionsById(req.params.id)
.then(data => {
if (data) {
data.forEach(async function(element) { // <-- note the async keyword here
await getLatLngofAsset(element.til_poi_id)
});
} else {
throw new Error('No data returned');
}
console.log(latLngOfAssets) // What I want to respond with res.json(latlngofassets)
})
}
This is a start - there are a couple more things we can tackle once you understand the relationship between the functions you have declared and the Promises they create & return.

Mongoose: write own methods with promise

I want to write some instance / static methods for a model, which uses the mongoose's API and do something before and after using the mongoose's API.
For example, I write my own Article.createArticle method, it checks the data before Article.create, and return article.toObject() after creation.
this is how I want my createArticle works:
Article.createArticle({someKeys: 'SomeData', ...})
.then(obj => {
// get plain object here
});
I tried to write something like this:
Article.Schema.static({
createArticle: function(data) {
return new Promise(function(resolve, reject){
checkDataKeys(data);
resolve(mongoose.model('Article').create(data)
.then(article => resolve(article.toObject()));
);
});
},
});
with this createArticle, I only get undefined in then,
I must get something wrong.
Also, in addition to make the createArticle work, is there any way to make the code more elegant?
Thanks.
I myself found a methods that works for me. Though I'm not very understand the mechanism, maybe I'll try to work on it later...
ArticleSchema.static({
createArticle: function(data) {
checkDataKeys(data); // pre works
return mongoose.model('Article').create(data)
.then(obj => {
return obj.toObject(); // afterworks
// here, just get the plain obj from doc
// and *return* it
});
},
});
update: after I searched something about Promise, maybe this could be better.
ArticleSchema.static({
createArticle: function(data) {
checkDataKeys(data); // pre works
return mongoose.model('Article').create(data)
.then(obj => {
return Promise.resolve(obj.toObject());
// Some say that this is totally same as directly return
})
.catch(err => {
return Promise.reject(err);
// if error, this will ensure the err will be caught
// for some async cases
});
},
});

NodeJS fs.appendFile not working within promise in mocha

I want to keep a log during my integration test suite. I'm testing that every 'item' is being compiled and logging how much time it took. I'm using node 4.3.
First of all I create the log file:
before(function() {
if (!fs.existsSync("./log.csv")) {
fs.writeFile("./log.csv", "Name; Time");
}
});
Then within each it block I would do this:
for (const item of items) {
it('compiles', function() {
return item.testCompile();
});
}
And item class has these methods:
testCompile() {
return this
.buildItem()
.then(result => {
// whatever testing stuff
});
}
buildItem() {
return this
.internalLogicForCompiling()
.then(result => {
// This is not appending anything
fs.appendFile("./log.csv", `${item.name}; ${result.compileTime}`);
return result;
});
}
So far the file is created but never updated... Any clue what I'm doing wrong?
PS: I assume if the file doesn't exists, fs should throw an error, however it doesn't.
Your code is generally ignoring the fact that your fs calls are asynchronous. Promises are not magic. If you use code that is asynchronous but does not use promises, you need to do more than plop that code in a promise can call it done.
The easiest way to deal with the issue would be to use fs.writeFileSync and fs.appendFileSync instead of the calls you make. Otherwise, you should write your before like this:
before(function(done) {
if (!fs.existsSync("./log.csv")) {
fs.writeFile("./log.csv", "Name; Time", done);
}
});
I've just added the done callback.
And buildItem could be something like this so that the promise it returns won't resolve before appendFile is done doing its work:
buildItem() {
return this
.internalLogicForCompiling()
.then(result => {
return new Promise((resolve, reject) => {
fs.appendFile("./log.csv", `${item.name}; ${result.compileTime}`, (err) => {
if (err) {
reject(err);
return;
}
resolve(result);
});
});
});
}

Unit Test with Mongoose

I'm new to Node.js, Mongoose, and testing in this environment. I have the following schema declared in a separate file.
Issue = mongoose.model("Issue", {
identifier: String,
date: String,
url: String,
name: String,
thumbnailURL: String
});
Then I have this method which simply returns all of the Issue instances in the MongoDB collection.
function issues(request, response) {
response.setHeader('Content-Type', 'text/json');
Issue.find().sort('date').exec(function(error, items) {
if (error) {
response.send(403, {"status": "error", "error:": exception});
}
else {
response.send(200, {"issues": items});
}
});
}
I've gotten this far through experimentation, and now I want to test it, but I've run into a problem. How do I go about testing it, without setting up a MongoDB connection, etc. I know that I can set all that stuff up, but that's an integration test. I want to write unit tests to test things like:
Does the function set the content type correctly
Does the function sort by the date field
Does the function return a 403 when an error occurs?
... and so on
I'm curious to see how I could refactor my existing code to make it more unit testable. I've tried maybe creating a second function that's called through, accepting the response and Item schema objects as parameters, but it doesn't feel right. Anyone have any better suggestions?
Mongoose model (your Issue) returns a new instance of the Query object. The new query instance has access to the exec method through the prototype. (mongoose 3.8~)
If you want to return an error you can write:
sinon.stub(mongoose.Query.prototype, "exec").yields({ name: "MongoError" }, null);
Using mocha with chaijs and sinonjs in my node code something like this method works for me:
var should = require('chai').should(),
sinon = require('sinon'),
mongoose = require('mongoose');
it('#issues() handles mongoosejs errors with 403 response code and a JSON error message', function (done) {
// mock request
var request = {};
// mock response
var response = {};
response.setHeader = function(header) {};
response.send = function (responseCode, jsonObject) {
responseCode.should.equal(403);
jsonObject.stats.should.equal('error');
// add a test for "error:": exception
done();
}
var mockFind = {
sort: function(sortOrder) {
return this;
},
exec: function (callback) {
callback('Error');
}
}
// stub the mongoose find() and return mock find
mongoose.Model.find = sinon.stub().returns(mockFind);
// run function
issues(request, response);
});
I'm not sure how to test the Content-Type, and I haven't tested this code myself, but I'm happy to help out if it doesn't work. It seems to make sense to me. Basically we just created a callback so we could move the response.send out of the actual custom logic, then we can test via that callback. Let me know if it doesn't work or make sense. You can use the links that the other guys posted to prevent having to connect to the db.
Issue = mongoose.model("Issue", {
identifier: String,
date: String,
url: String,
name: String,
thumbnailURL: String
});
function issues(callback, request, response) {
Issue.find().sort('number').exec(function(error, items) {
if (error) {
callback(403, {"status": "error", "error:": exception});
}
else {
callback(200, {"issues": items});
}
});
}
//Note: probably don't need to make a whole new `sender` function - depends on your taste
function sender(statusCode, obj) {
response.setHeader('Content-Type', 'text/json');
response.send(statusCode, obj);
}
//Then, when you want to implement issues
issues(sender, request, response);
//The tests - will depend on your style and test framework, but you get the idea
function test() {
//The 200 response
issues(function(code, obj) {
assert(code === 200);
assert(obj.issues === ??); //some testable value, or just do a truthy test
//Here, you can also compare the obj.issues item to the proper date-sorted value
});
//The error response
issues(function(code, obj) {
assert(code === 403);
assert(code.status === 'error');
});
}
A good place to start would be:
Investigate the concepts around stubs and mocks, and test doubles.
Check out Sinon.js which is the Mocking framework of choice for Node.JS

Testing asynchronous middleware functionality with Mongoose

I'm using a save middleware in Mongoose to create a log of activity in the DB whenever some action is taken. Something like
UserSchema.post("save", function (doc) {
mongoose.model("Activity").create({activity: "User created: " + doc._id});
});
This appears to work fine, but the problem is that I can't test it because there is no way to pass a callback to post (which probably would not make sense). I test this out using mocha with:
User.create({name: "foo"}, function (err, user) {
Activity.find().exec(function (err, act) {
act[0].activity.should.match(new RegExp(user._id));
done();
});
});
The problem is that the Activity.create apparently does not finish before .find is called. I can get around this by wrapping .find in setTimeout, but this seems hacky to me. Is there any way to test asynchronous mongoose middleware operations?
Unfortunately, there's not a way to reliably interleave these two asynchronous functions in the way you'd like (as there aren't threads, you can't "pause" execution). They can complete in an inconsistent order, which leaves you to solutions like a timeout.
I'd suggest you wire up an event handler to the Activity class so that when an Activity is written/fails, it looks at a list of queued (hashed?) Activities that should be logged. So, when an activity is created, add to list ("onactivitycreated"). Then, it will eventually be written ("onactivitywritten"), compare and remove successes maybe (not sure what makes sense with mocha). When your tests are complete you could see if the list is empty.
You can use util.inherits(Activity, EventEmitter) for example to extend the Activity class with event functionality.
Now, you'll still need to wait/timeout on the list, if there were failures that weren't handled through events, you'd need to handle that too.
Edit -- Ignore the suggestion below as an interesting demo of async that won't work for you. :)
If you'd like to test them, I'd have a look at a library like async where you can execute your code in a series (or waterfall in this case) so that you can first create a User, and then, once it completes, verify that the correct Activity has been recorded. I've used waterfall here so that values can be passed from one task to the next.
async.waterfall([
function(done) {
User.create({name: "foo"}, function (err, user) {
if (err) { done(err); return; }
done(null, user._id); // 2nd param sent to next task as 1st param
});
},
function(id, done) { // got the _id from above
// substitute efficient method for finding
// the corresponding activity document (maybe it's another field?)
Activity.findById(id, function (err, act) {
if (err) { done(err); return; }
if (act) { done(null, true);
done(null, false); // not found?!
});
}
], function(err, result) {
console.log("Success? " + result);
});
Async post-save middleware will apparently be available in Mongoose 4.0.0:
https://github.com/LearnBoost/mongoose/issues/787
https://github.com/LearnBoost/mongoose/issues/2124
For now, you can work around this by monkey-patching the save method on the document so that it supports async post-save middleware. The following code is working for me in a similar scenario to yours.
// put any functions here that you would like to run post-save
var postSave = [
function(next) {
console.log(this._id);
return next();
}
];
var Model = require('mongoose/lib/model');
// monkey patch the save method
FooSchema.methods.save = function(done) {
return Model.prototype.save.call(this, function(err, result) {
if (err) return done(err, result);
// bind the postSave functions to the saved model
var fns = postSave.map(function(f) {return f.bind(result);});
return async.series(fns,
function(err) {done(err, result);}
);
});
};

Resources