I am new to testing. I am trying to unit-test a method that calls 2 other methods. I tried stubbing those 2 methods, but it looks like the original methods are still getting called. I am trying to test whether my method adds the object the savePoll method returns to the user.polls array.
test.js
var UserStub = sinon.stub();
var PollStub = sinon.stub();
var OptionStub = sinon.stub();
var saveOptionsStub = sinon.stub();
var savePollStub = sinon.stub();
var users = proxyquire('../controllers/users', {
'../models/user': UserStub,
'../models/poll': PollStub,
'../models/option': OptionStub
});
beforeEach(function() {
res = {
json: sinon.spy(),
send: sinon.spy(),
status: function(responseStatus) {
return this;
}
};
req = {
params: {
id: 1
}
};
UserStub.save = function(callback) {
callback(null, req.body);
};
});
describe('add a poll', function() {
it('should add the poll', function() {
req.body = {
name: 'Poll1',
options: [{
text: 'option1',
votes: 0
}, {
text: 'option2',
votes: 1
}]
};
var user = {};
user.polls = [];
UserStub.findById = function(query, callback) {
callback(null, user);
};
sinon.stub(require('../controllers/users'), 'saveOptions').returns([OptionStub, OptionStub]);
sinon.stub(require('../controllers/users'), 'savePoll').returns({});
users.addPoll(req, res);
expect(user.polls).to.equal('[{}]');
});
});
users.js
addPoll: function(req, res) {
var self = this;
User.findById(req.params.user_id, function(err, user) {
if (err) return res.status(400).json(err);
self.saveOptions(req.body.options)
.then(function(optionsArr) {
var pollName = req.body.name;
self.savePoll(pollName, optionsArr)
.then(function(poll) {
user.polls.push(poll);
user.save(function(err, user) {
if (err) return res.status(500).json(err);
res.json(poll);
});
}, function(err) {
return res.status(500).json(err);
});
}, function(err) {
return res.status(400).json(err);
});
});
},
savePoll: function(pollName, optionsArr) {
return new Promise(function(resolve, reject) {
var poll = new Poll();
poll.name = pollName;
poll.options = optionsArr.slice();
poll.save(function(err, poll) {
if (err) {
reject(err);
} else {
resolve(poll);
}
});
});
},
saveOptions: function(options) {
var optsArr = [];
var promises = options.map(function(opt) {
return new Promise(function(resolve, reject) {
var option = new Option(opt);
return option.save(function(err, option) {
if (err) {
reject(Error(err));
} else {
resolve(optsArr.push(option));
}
});
});
});
return Promise.all(promises).then(function() {
return optsArr;
}).catch(function(err) {
console.log(err);
});
}
Related
I currently have the code below, which was created from a previous question I posted last year here.
var imaps = require('imap-simple');
var configBauerEmail = {
imap: {
user: '********#hotmail.com',
password: '******',
host: 'imap-mail.outlook.com',
port: 993,
tls: true,
authTimeout: 30000
}
};
module.exports = {
'delete any existing emails...': function () {
imaps.connect(configBauerEmail).then(function (connection) {
connection.openBox('INBOX').then(function () {
var searchCriteria = ['ALL'];
var fetchOptions = { bodies: ['TEXT'], struct: true
};
return connection.search(searchCriteria, fetchOptions);
})
//Loop over each message
.then(function (messages) {
let taskList = messages.map(function (message) {
return new Promise((res, rej) => {
var parts = imaps.getParts(message.attributes.struct);
parts.map(function (part) {
return connection.getPartData(message, part)
.then(function (partData) {
//Display e-mail body
if (part.disposition == null && part.encoding != "base64") {
console.log(partData);
}
//Mark message for deletion
connection.addFlags(message.attributes.uid, "\Deleted", (err) => {
if (err) {
console.log('Problem marking message for deletion');
rej(err);
}
res();
});
});
});
});
});
return Promise.all(taskList).then(() => {
connection.imap.closeBox(true, (err) => {
if (err) {
console.log(err);
}
});
connection.end();
});
});
});
},
'send email to seller and wait for mailbox notification': function (browser) {
browser.url(browser.launch_url + browser.globals.testDealerBfsAdevertEmailTest);
browser.notificationDismissal();
browser.cmpDismissal();
browser.emailFunctionality.emailTheSeller();
browser.browserEnd();
},
'get new email info': function() {
const createPromise = ms => new Promise((resolve, reject) => {
setTimeout(() => resolve(ms), ms);
});
function findUnseenEmails(connection) {
return connection.openBox('INBOX').then(function () {
var searchCriteria = ['UNSEEN'];
var fetchOptions = {
bodies: ['HEADER','TEXT'],
markSeen: false
};
return connection.search(searchCriteria, fetchOptions).then(function (results) {
var subjects = results.map(function (res) {
return res.parts.filter(function (part) {
return part.which === 'HEADER';
})
[0].body.subject[0];
});
console.log(subjects);
if (subjects.length > 0) {
connection.end();
return subjects;
} else {
return createPromise(60000).then(function() {
return findUnseenEmails(connection);
});
}
});
});
}
imaps.connect(configBauerEmail).then(function (connection) {
return findUnseenEmails(connection);
})
.then((subjects) => console.log('finished', subjects));
},
};
This works OK, in that the following loop that was added will loop over every 60 seconds checking that the email has 'arrived' in the mailbox.
if (subjects.length > 0) {
connection.end();
return subjects;
} else {
return createPromise(60000).then(function() {
return findUnseenEmails(connection);
});
}
});
However, at present if the email sending process has failed and the email account does not receive the email, the test will carry on looping continuously until the test is physically stopped.
What I'd now like to do is set some sort of 'time limit' within this loop, so that if the email has not arrived in the mailbox within 30 minutes the test will fail.
I appreciate that this will involve a limit setting in the loop above, but I've tried it in several locations within the loop and I can't get it to work.
Any help would be greatly appreciated. Thanks.
I'm trying to write a wrapper class called Mongo. When I call getCollection() inside insert(), but I'm getting 'TypeError: this.getCollection is not a function'.
const mongoClient = require('mongodb').MongoClient;
const connectionString = process.env.MONGODB_CONNECTION_STRING;
const mongoOptions = {
connectTimeoutMS: 500,
autoReconnect: true
};
function Mongo(dbName, collectionName) {
this.dbName = dbName;
this.collectionName = collectionName;
this.db = null;
this.collectionCache = {};
this.getDB = function () {
return new Promise(function (resolve, reject) {
if (this.db == null) {
mongoClient.connect(connectionString, mongoOptions, function (err, db) {
if (err) reject(err);
this.db = db.db(this.dbName);
resolve(this.db);
});
} else {
resolve(this.db);
}
});
};
this.getCollection = function () {
return new Promise(function (resolve, reject) {
if (this.collectionName in this.collectionCache) {
resolve(this.collectionCache[this.collectionName]);
} else {
getDB().then(function(db) {
db.collection(this.collectionName, function (err, collection) {
if (err) reject(err);
this.collectionCache[this.collectionName] = collection;
resolve(collection);
});
}, function (err) {
reject(err);
});
}
});
};
this.insert = function(docs) {
return new Promise(function (resolve, reject) {
this.getCollection().then(function(collection) {
collection.insert(docs, function(err, results) {
if (err) reject(err);
resolve(results);
});
});
}, function (err) {
reject(err);
});
}
}
module.exports = Mongo;
How this class is instantiated, and the insert method is called.
const assert = require('assert');
const Mongo = require('../data/mongo');
describe('MongoTest', function() {
it('TestInsert', function() {
var mongo = new Mongo('testdb', 'humans');
var testDoc = {
_id: 1100,
name: 'tommy',
tags: ['cool', 'interesting']
};
mongo.insert(testDoc).then(function(result){
assert.equal(result._id, 1100);
});
})
})
Where you're calling the getCollection function, this no longer refers to the Mongo object, it refers to the callback function you've passed as a parameter to the Promise.
There are many ways of fixing that. Here are a few:
Keeping a reference to the object:
this.insert = function(docs) {
var self = this;
return new Promise(function (resolve, reject) {
self.getCollection().then(function(collection) {
collection.insert(docs, function(err, results) {
if (err) reject(err);
resolve(results);
});
});
}, function (err) {
reject(err);
});
}
Binding the context:
this.insert = function(docs) {
var callbackFunction = function (resolve, reject) {
this.getCollection().then(function(collection) {
collection.insert(docs, function(err, results) {
if (err) reject(err);
resolve(results);
});
});
};
return new Promise(callbackFunction.bind(this), function (err) {
reject(err);
});
}
Update your insert function as follows -
this.insert = function(docs) {
var _self = this;
return new Promise(function (resolve, reject) {
_self.getCollection().then(function(collection) {
collection.insert(docs, function(err, results) {
if (err) reject(err);
resolve(results);
});
});
}, function (err) {
reject(err);
});
}
You are getting this error because you are calling getCollection inside callback function where this does not refer to Mongo class. So you need to store the Mongo class reference in a variable and then use it.
I created two collections,one for enterprise and another for employees,their schema is as follows,
var mongoose= require('mongoose');
var Enterprise= new mongoose.Schema({
name:{type:String},
email:{type:String},
sector:{type:String},
employees: {type:Number,default:0}
});
module.exports={
Enterprise:Enterprise
};
var mongoose = require('mongoose');
var employee = new mongoose.Schema({
enterprise:{type: String},
name:{type:String},
email:{type:String},
password:{type:String},
gender:{type:String},
});
module.exports = {
employee:employee
};
my add employee route,
var mongoose = require('mongoose');
var q = require('q');
var employee = mongoose.model('employee');
var enterprise = mongoose.model('enterprise');
var addEmployee = function(req, res) {
newEmployee = new employee();
newEmployee.enterprise = req.params.enterprise;
newEmployee.name = req.params.name;
newEmployee.email = req.params.email;
newEmployee.gender = req.params.gender;
function detailSave() {
var deferred = q.defer();
newEmployee.save(function(err, data) {
if (err) {
res.send(500);
console.log('couldnt save employee details');
deferred.reject({errmessage: 'couldnt save employee details', err: err});
} else {
res.send(200);
deferred.resolve({data: data});
}
});
return deferred.promise;
}
function incrementEmployee(doc) {
var deferred = q.defer();
enterprise.findOneAndUpdate({ 'name': doc.enterprise }, { $inc: { 'employees': 1 } },
function(err, num) {
if (err) {
deferred.reject({errmessage: 'couldnt incrementEmployee', err: err});
res.send(500);
console.log('couldnt incrementEmployee');
} else {
res.send(200);
deferred.resolve({num:num});
}
});
return deferred.promise;
}
detailSave()
.then(incrementEmployee)
.then(function(success) {
console.log('success', success);
res.json(200, success);
})
.fail(function(err) {
res.json(500, err);
})
.done();
};
module.exports = {
addEmployee: addEmployee
};
The problem is when I add an employee, the employees field in enterprise collection doesn't increment
I think your query is not working since doc.enterprise is null
On the basis of your comment.
Try to give your query like this {'name': doc.data.enterprise}
function incrementEmployee(doc) {
var deferred = q.defer();
enterprise.findOneAndUpdate({
'name': doc.data.enterprise
}, {
$inc: {
'employees': 1
}
},
function(err, num) {
if (err) {
deferred.reject({
errmessage: 'couldnt incrementEmployee',
err: err
});
res.send(500);
console.log('couldnt incrementEmployee');
} else {
res.send(200);
deferred.resolve({
num: num
});
}
});
return deferred.promise;
}
I have a function that is supposed to add new item each time there is a match as below:
function getTimeEntriesFromWorksnap(error, response, body) {
//console.log(response.statusCode);
var counter = 1;
if (!error && response.statusCode == 200) {
parser.parseString(body, function (err, results) {
var json_string = JSON.stringify(results.time_entries);
var timeEntries = JSON.parse(json_string);
_.forEach(timeEntries, function (timeEntry) {
_.forEach(timeEntry, function (item) {
Student.findOne({'worksnap.user.user_id': item.user_id[0]})
.populate('user')
.exec(function (err, student) {
if (err) {
throw err;
}
var newTimeEntry = _pushToObject(student.worksnap.timeEntries, item);
student.worksnap.timeEntries = {};
student.worksnap.timeEntries = newTimeEntry;
student.save(function (err) {
if (err) {
//return res.status(400).send({
// message: errorHandler.getErrorMessage(err)
//});
} else {
//res.json(item);
}
});
});
});
});
});
}
}
For some reason it is only inserting once for each student that it finds.
And my Student schema looks like this:
var StudentSchema = new Schema({
firstName: {
type: String,
trim: true,
default: ''
//validate: [validateLocalStrategyProperty, 'Please fill in your first name']
},
lastName: {
type: String,
trim: true,
default: ''
//validate: [validateLocalStrategyProperty, 'Please fill in your last name']
},
worksnap: {
user: {
type: Object
},
timeEntries: {
type: Object
},
}
});
Any solution?
My Guess is its always pushing the last one... closures...
function getTimeEntriesFromWorksnap(error, response, body) {
//console.log(response.statusCode);
var counter = 1;
if (!error && response.statusCode == 200) {
parser.parseString(body, function (err, results) {
var json_string = JSON.stringify(results.time_entries);
var timeEntries = JSON.parse(json_string);
_.forEach(timeEntries, function (timeEntry) {
_.forEach(timeEntry, function (item) {
saveStudent(item);
});
});
});
}
}
Below is saveStudent function
function saveStudent(item) {
Student.findOne({
'worksnap.user.user_id': item.user_id[0]
})
.populate('user')
.exec(function(err, student) {
if (err) {
throw err;
}
var newTimeEntry = _pushToObject(student.worksnap.timeEntries, item);
student.worksnap.timeEntries = {};
student.worksnap.timeEntries = newTimeEntry;
student.save(function(err) {
if (err) {
//return res.status(400).send({
// message: errorHandler.getErrorMessage(err)
//});
} else {
//res.json(item);
}
});
});
}
OR
wrap it inside a closure...
function getTimeEntriesFromWorksnap(error, response, body) {
//console.log(response.statusCode);
var counter = 1;
if (!error && response.statusCode == 200) {
parser.parseString(body, function(err, results) {
var json_string = JSON.stringify(results.time_entries);
var timeEntries = JSON.parse(json_string);
_.forEach(timeEntries, function(timeEntry) {
_.forEach(timeEntry, function(item) {
(function(item){
Student.findOne({
'worksnap.user.user_id': item.user_id[0]
})
.populate('user')
.exec(function(err, student) {
if (err) {
throw err;
}
var newTimeEntry = _pushToObject(student.worksnap.timeEntries, item);
student.worksnap.timeEntries = {};
student.worksnap.timeEntries = newTimeEntry;
student.save(function(err) {
if (err) {
//return res.status(400).send({
// message: errorHandler.getErrorMessage(err)
//});
} else {
//res.json(item);
}
});
});
}(item))
});
});
});
}
}
Can you use async library and check out...
var async = require('async');
async.map(timeEntries, function (timeEntry, next) {
async.map(timeEntry, function (item, next) {
//your code.
});
});
I'm new to Promises so I might be doing something stupid here, but I can't seem to figure it out.
Just so I know I'm on the right path, a bit of information upfront. I have an authenticate method which returns a promise:
APIWrapper.prototype.authenticate = function() {
var self = this;
return new Promise(function(resolve, reject) {
request({
uri: self.httpUri + '/auth/authenticate',
method: 'GET',
headers: {
auth_user: self.user,
auth_pass: self.pass,
auth_appkey: self.appkey
}
}, function(err, res, body) {
if (err) return reject(err);
self.parser.parseXML(body, function(err, result) {
if (err) return reject(err);
if (result.error) { return reject(result.error) }
self.token = result.auth.token[0];
return resolve(result);
});
});
});
};
I chain this with .getDashboards() like this:
wrapper.authenticate().then(function() {
wrapper.getDashboards();
}).then(function(result) {
console.log('result', result);
});
.getDashboards() also returns a promise:
APIWrapper.prototype.getDashboards = function() {
var self = this;
return new Promise(function(resolve, reject) {
request({
url: self.httpUri + '/user/dashboard',
method: 'GET',
headers: {
auth_appkey: self.appkey,
auth_token: self.token
}
}, function(err, res, body) {
if (err) { return reject('Could not connect to the API endpoint'); };
self.parser.parseXML(body, function(err, data) {
var dashboards = [];
if(err) { return reject(err); }
if(data.error) { return reject(data.error); }
for(var i = 0; i < data.Dashboards.Dashboard.length; i++) {
dashboards.push(self.getDashboard(data.Dashboards.Dashboard[i]));
}
// returns early here
resolve(dashboards);
});
});
});
};
With the .getDashboard() method like this at the moment:
APIWrapper.prototype.getDashboard = function(db) {
var dashboard = {};
dashboard.title = db.Title[0];
dashboard.id = db.$.id;
console.log(dashboard);
return dashboard;
};
What happens with this code is that it returns the result before it returns the dashboards. I suspect the resolve() in .getDashboards() doesn't wait for the for loop to finish? Do I need to use promises in the .getDashboard() method as well, or how would I wait for it to finish before resolving my .getDashboards() promise?
Output:
> result undefined
{ title: 'Dashboard 1', id: '3271' }
{ title: 'Dashboard 2', id: '3272' }
{ title: 'Dashboard 3', id: '3273' }
I'm using this Promise implementation at the moment: https://github.com/then/promise
You need to return the promise to have it chained :
wrapper.authenticate().then(function() {
return wrapper.getDashboards();
}).then(function(result) {
console.log('result', result);
});
In your case, it can be simplified as
wrapper.authenticate()
.then(wrapper.getDashboards)
.then(function(result){
console.log('result', result);
});
You also don't seem to handle errors. The then library seems very raw on this point, so you should probably add a second argument :
wrapper.authenticate()
.then(wrapper.getDashboards, onAuthenticateError)
.then(function(result){
console.log('result', result);
}, onDashboardError);