I have a simple Mongoose schema that I'm testing with Mocha; when I run the test with 'success' callback it executes normally, however when the last test executes it fails and appears to run the test again (I get two conclusions, one which populates the error object and the second one which returns null in the error object.) Running the two tests below results in the following output:
Cabinet:
â should return all authorized
â should return not authorized <-- it succeeds the first time?
1) should return not authorized
2 passing (42ms)
1 failing <--- what? there are only two tests
1) Cabinet: should return not authorized :
Uncaught AssertionError: expected null to exist <--- see test
THIS TEST REPEATS
it("should return not authorized error ", function(done){
var newCabinet = {
name: "A new cabinet",
description: "Some remote cabinet",
authorizedBorrowers : ["imatest","imanothertest"],
cabinetInventory : []
};
Cabinet.newCabinet(newCabinet, function(err, cabinet){
if (err) {
console.log("Unable to create cabinet");
done(err);
}
Cabinet.isAuthorizedBorrower(cabinet._id, "bob", function(cberr, borrower){
should.exist(cberr); <-- 'expected null to exist' points here
done();
});
});
});
THIS TEST WORKS
it("should not return unauthorized error ", function(done){
var newCabinet = {
name: "A new cabinet",
description: "Some remote cabinet",
authorizedBorrowers : ["imatest","imanothertest"],
cabinetInventory : []
};
Cabinet.newCabinet(newCabinet, function(err, cabinet){
if (err) {
console.log("Unable to create cabinet");
done(err);
}
//console.log("ID " + cabinet._id)
Cabinet.isAuthorizedBorrower(cabinet._id, "imatest", function(cberr, borrower){
should.not.exist(cberr);
done();
});
});
});
The schema
var cabinetSchema = new Schema({
name: String,
description: String,
thumbnail : Buffer,
authorizedBorrowers : [],
defaultCheckout : {type : Number, default: 0} // default checkout mins = no time
});
var hasBorrower = function(cabinet, borrower){
if (cabinet===null) return false;
if (cabinet.authorizedBorrowers.length === 0) return false;
return (cabinet.authorizedBorrowers.indexOf(borrower) >= 0)
}
cabinetSchema.statics.isAuthorizedBorrower = function(cabinet_id, borrowername, cb ){
this.findOne({_id: cabinet_id}, function(err, cabinet){
if (err) cb(err,null);
if (!hasBorrower(cabinet, borrowername)) cb(new Error(errorMsgs.borrowerNotAuthorized),null);
cb(null,borrowername);
});
};
Whenever you do this, add a return; to avoid calling the done callback twice. This is for mocha but also for general node.js callback handling.
if (err) {
console.log("Unable to create cabinet");
done(err);
return;
}
Same problem in your Cabinet schema:
if (err) cb(err,null);
That needs a return or it will invoke the callback twice and cause chaos (also affectionately known amongst the node.js blogosphere as one flavor of "releasing Zalgo").
Related
Hello guys i'm newbie with testing
I'm using socket.io and I want to simulate throwing error by my function when something happens on the insertion.
socket.on('request', function(request) {
bookingService.addBooking(request)
.then(function (booking) {
winston.info('Client Order saved');
io.emit('order to driver', {user: request[2], random : request[3]});
})
.catch(function (err) {
winston.error(' Client error on save order ==> '+err);
});
});
addBooking
function addBooking(msgParam) {
var deferred = Q.defer();
db.booking.insert(
{ user : msgParam[2],
adress : msgParam[0],
random : msgParam[3],
driver : [],
isTaken : false,
isDone : false,
declineNbr : 0 ,
createdOn : msgParam[1],
createdBy : msgParam[2],
updatedOn : null,
updatedBy : []},
function (err, doc) {
if (err) deferred.reject(err.name + ': ' + err.message);
deferred.resolve();
});
return deferred.promise;
}
I tried to just test the addBokking function
it('should throw error if something wrong happend on adding new order ', function(done){
(bookingService.addBooking(request)).should.throw()
done();
});
but I get this error
AssertionError: expected { state: 'pending' } to be a function
You can use the following syntax with chai:
it("throw test", () => {
expect(()=>{myMethodThatWillThrowError()}).to.throw();
});
For promises, you can use the following pattern:
it("should throw on unsuccessfull request", (done: MochaDone) => {
repo.patch({
idOrPath: "Root/Sites/Default_Site",
content: ConstantContent.PORTAL_ROOT,
}).then(() => {
done("Should throw"); // The test will fail with the "Should throw" error
}).catch(() => {
done(); // The test will report success
});
});
You are checking the promise and not the result
This error:
AssertionError: expected { state: 'pending' } to be a function
... means you are checking the promise returned from the addBooking function and not the resolved/rejected result of the promise.
With chai-as-promised you can do that easily!
With chai-as-promised these should work for example (from the documentation):
return promise.should.be.rejected;
return promise.should.be.rejectedWith(Error); // other variants of Chai's `throw` assertion work too.
or in your specific case (after installing and connecting chai-as-promised), this should work:
(bookingService.addBooking(request)).should.be.rejected
(maybe should.throw() will work with chai-as-promised too, I'm less familiar with it)
Check it out here: chai-as-promised
I am creating a command line app using nodejs. For some reason whenever I enter a track (node liri.js spotify-this-song "enter track") i get a response of a random band called Nocturnal rites, song named "something undefined", and album called "grand illusion". Does anyone know where I am wrong, or why I am getting these responses?
function spotifyIt(song) {
spotify.search({ type: 'track', query: song }, function(err, data) {
if ( err ) {
console.log('Error occurred: ' + err);
return; //from spotify npm docs
}
else{
var songInfo = data.tracks.items[0];
var songResult = console.log(songInfo.artists[0].name)
console.log(songInfo.name)
console.log(songInfo.album.name)
console.log(songInfo.preview_url)
console.log(songResult);
};
});
}
Nevermind, I figured it out. had to change the query to the correct params[]... i.e it ended up looking like this
function spotifyIt() {
spotify.search({ type: 'track', query: params[1] }, function(err, data) {
if ( err ) {
console.log('Error occurred: ' + err);
return; //from spotify npm docs
}
else{
var songInfo = data.tracks.items[0];
var songResult = console.log(songInfo.artists[0].name)
console.log(songInfo.name)
console.log(songInfo.album.name)
console.log(songInfo.preview_url)
console.log(songResult);
};
});
}
I had a global variable var params = process.argv.slice(2); and a switch statement with another params[1], so that it ended up calling the fourth parameter i.e. where the song title was named in the terminal
switch(params[0]) {
case "my-tweets":
myTweets();
break;
case "spotify-this-song":
if(params[1]){ //if a song is put named in 4th paramater go to function
spotifyIt();
} else { //if blank call it blink 182's "whats my age again"
spotifyIt("What\'s my age again");
}
break;
I'm running some tests using Mocha and chai. One of the tests is hanging at a .should.be.deep.equal call.
Here's the test code:
// Make the fake connection before running tests
before(function(done) {
mongoose.connect('mongodb://fake.test/TestingDB', function(err) {
done(err);
});
});
// Test Cases
describe('Testing the functions that deal with users and locations:', function() {
// Test Setup
var req = {};
beforeEach(function(done) {
mockgoose.reset()
async.parallel([function(callback){
sensors.create(testData.deviceData, function(err, model) {
if (err) {console.log(err)}
callback();
});
}, function(callback) {
locations.create(testData.locationData, function(err, model) {
if (err) {console.log(err)}
callback();
});
}], function(err) {
done();
});
});
afterEach(function(done) {
mockgoose.reset();
done();
});
// Tests
describe('function locationList', function() {
it('should list the test location', function(done) {
dbFunctions.locationList(req, function(result) {
console.log(result) //Prints the whole result
console.log(testData.locationList)
result.should.exist; //This doesn't cause it to hang
result.should.be.deep.equal(testData.locationList) //hangs here
done(result);
});
})
})
});
And here's the function it's testing:
exports.locationList = function(req, callback) {
listLocations().then(
function(data) {
callback(data);
},
function(err) {
console.log('Error Retrieving Location Information: ' + err);
callback(err);
});
};
As I note in the comments, the results object exists and gets printed to the console. results.should.exist; doesn't throw an exception and if I comment out everything but it the test works fine. For some weird reason, despite both the testData.locationList and result object existing, the test times out. I have 14 other tests that use the exact same syntax without any problems. Does anyone know what could be causing this to happen for this specific test?
Here's the output from the tests:
Testing the functions that deal with users and locations:
function locationList
[ { devices: {},
address: '123 Fake St, Waterloo, On',
location: 'Unittest',
owner: 'unit#test.com',
_id: '-1' } ]
[ { devices: {},
address: '123 Fake St, Waterloo, On',
location: 'Unittest',
owner: 'unit#test.com',
_id: '-1' } ]
1) should list the test location
0 passing (2s)
1 failing
1) Testing the functions that deal with users and locations: function locationList should list the test location:
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.
at null.<anonymous> (C:\Users\My Name\AppData\Roaming\npm\node_modules\mocha\lib\runnable.js:189:19)
Extending the timeout doesn't work. Nor does putting something random (ie. the integer 1 in the .should.be.deep.equal() function.
My guess is that the callback in exports.locationList is called synchronously, and that your test case is actually failing, throwing an exception, which never gets caught because the callback is (synchronously) called from within a promise handler (more info here).
Try and see if this works better:
dbFunctions.locationList(req, function(result) {
setImmediate(function() {
console.log(result) //Prints the whole result
console.log(testData.locationList)
result.should.exist; //This doesn't cause it to hang
result.should.be.deep.equal(testData.locationList) //hangs here
done(result);
});
});
// or use a module like dezalgo (https://github.com/npm/dezalgo)
The underlying cause may be mockgoose.
Also, you're not using the proper Node.js conventions where the first argument to a callback function is "reserved" for errors. In other words, your code should look similar to this:
if (err) {
callback(err);
} else {
callback(null, data);
}
You're now passing both errors and data as the first argument.
A guess: Maybe the issue lies with mongoose decorating the result with it's own functions/members, and one of them somehow getting stuck in an infinite loop when chai tries to enumerate them all to perform a deep comparison.
I am trying to save a new Document (user) in my MongoDb and I use callback. The code runs and goes until save the user, but after that I get an error.So I can save user. I have the following code:
function saveUser(userName, socialMediaType, socialMediaID, setDocNumber, callback){
var user;
if(socialMediaType == "fbUID"){
user = new users({
userName: userName,
userEmail: 'userEmail',
teams:[],
fbUID : socialMediaID
});
}else
if(socialMediaType =="google"){
//do the same
}
var query = {}
query["'"+ socialMediaType +"'" ] = socialMediaID
users.findOne(query, function(err, userFound){
if (err) { // err in query
log.d("Error in query FoundUser", err)
log.d("User Found", userFound)
}else
if(userFound == undefined){ //if user does not exist
user.save(function(err, user){
if(err) return console.error(err);
log.d("user saved", user);
currentSession = sessionOBJ.login(user._id, socialMediaID);
callback(currentSession,"created")
});
}else{
currentSession = sessionOBJ.login(userFound._id, socialMediaID);
callback(currentSession,"logged")
}
});
}
I call the function above through this code:
f(fbUID !== undefined){
userModelOBJ.saveUser(userName,"fbUID", fbUID, function(currentSession, status) {
res.send({"status":status,
"sessionID": currentSession.sessionID,
"expires" : currentSession.date});
});
I am getting this error :
The error is in the line :
callback(currentSession,"created")
What could be the problem?
I already did many researchers but this is a specific case.
Your saveUser() call is missing the setDocNumber argument. It looks like you're not using it in your code though, so you might be able to safely remove it. If you are using it somewhere else (that you haven't shown) then you need to do some argument checking at the top of saveUser() to support optional arguments.
Here is my code :
server.get(url_prefix + '/user/:user_id/photos', function(req, res, next) {
if (!req.headers['x-session-id']) {
res.send({
status: {
error: 1,
message: "Session ID not present in request header"
}
})
} else {
User.findOne({
session_id: req.headers['x-session-id']
}, function(err, user) {
if (user) {
var user_id = req.params.user_id
Album.find({userId : user_id})
.populate('images')
.exec(function (err, albums) {
if (albums) {
albums.forEach(function(album, j) {
var album_images = album.images
album_images.forEach(function(image, i) {
Like.findOne({imageID : image._id, userIDs:user._id}, function(err,like){
if(like){
albums[j].images[i].userLike = true;
}
})
})
})
return res.send({
status: {
error: 0,
message: "Successful"
},
data: {
albums: albums
}
})
} else
return notify_error(res, "No Results", 1, 404)
})
}
else {
res.send({
status: {
error: 1,
message: "Invalid Session ID"
}
})
}
})
}
})
I am trying to add a extra value (albums[j].images[i].userLike = true;) to my images array, which is inside album array.
The problem is return res.send({ send the data before we get response from the foreach
How can I make it work, so that return should happen only after foreach has completed all the iteration
You will have to wait with invoking res.send until you fetched all the likes for all the images in each of the albums. E.g.
var pendingImageLikes = album_images.length;
album_images.forEach(function(image, i) {
Like.findOne({imageID : image._id, userIDs:user._id}, function(err,like){
if (like) {
albums[j].images[i].userLike = true;
}
if (!--pendingImageLikes) {
// we fetched all likes
res.send(
// ...
);
}
});
You might need to special case for album_images.length === 0.
Also, this does not take into account that you have multiple albums with multiple images each. You would have to delay res.send there in a very similar way to make this actually work. You might want to consider using a flow control library like first (or any other of your preference, just search for "flow control library") to make this a bit easier.
Also, you might want to consider not relying on semicolon insertion and manually type your semicolons. It prevents ambiguous expressions and makes the code easier to read.
Since you need your code to wait until all of the find operations have completed, I'd suggest you consider using the async package, and specifically something like each (reference). It makes using async loops cleaner, especially when dealing with MongoDB documents and queries. There are lots of nice features, including the ability to sequentially perform a series of functions or waterfall (when you want to perform a series, but pass the results from step to step).
> npm install async
Add to your module:
var async = require("async");
Your code would look something like this:
albums.forEach(function(album, j) {
async.each(album.images, function(album, done) {
Like.findOne({imageID: image._id, userIDs:user._id}, function(err, like){
if(!err && like){
albums[j].images[i].userLike = true;
}
done(err); // callback that this one has finished
})
})
}, function (err) { // called when all iterations have called done()
if (!err) {
return res.send({
status: {
error: 0,
message: "Successful"
},
data: {
albums: albums
}
});
}
return notify_error(res, "No Results", 1, 404);
});
});