I am testing an express route handler which makes use of mongoose. My route handler code is the following.
// Require models.
var Comment = require('../models/commentModel');
var User = require('../models/userModel');
var Post = require('../models/postModel');
// Require mongoose and set bluebird to handle its promises.
var mongoose = require('mongoose');
mongoose.Promise = require('bluebird');
// Creates a comment.
exports.create_comment = function(req, res) {
var comment = new Comment();
return comment.save().then(function(createdComment) {
res.json(createdComment);
var promises = [
User.findById(userId).exec(),
Post.findById(postId).exec()
];
// Should resolve to [{ user: {/* */} }, { post: {/* */} }]
var promisedDocs = Promise.all(promises);
// Function provided to Array.map().
function pushAndSave(obj) {
// Extract the doc from { doc: {/* */} }
var doc = obj[Object.keys(obj)[0];
doc.comments.push(createdComment);
return doc.save();
}
// Return promise and process the docs returned by resolved promise.
return promisedDocs.then(function(results) {
results.map(pushAndSave);
});
})
.catch(function(err) {
res.json(err);
});
}
The logic I am trying to test is that when everything goes right, the calls to the appropriate functions are made. Basically, I am expecting the following:
comment.save()
, User.findById().exec()
, Post.findById().exec()
, user.save()
, and post.save()
to be called.
To test this, I am using mocha, chai, sinon, sinon-mongoose, sinon-stub-promise, and node-mocks-http. This is my test (I am obviating the setup).
it('Makes all the appropriate calls when everyting goes right', function() {
// Mock through sinon-mongoose
var userMock = sinon.mock(User);
var postMock = sinon.mock(Post);
userMock
.expects('findById')
.chain('exec')
.resolves({
user: {/**/}
});
postMock
.expects('findById')
.chain('exec')
.resolves({
post: {/**/}
});
// Stubbing calls to Model#save.
var saveComment = sinon.stub(Comment.prototype, 'save');
var saveUser = sinon.stub(User.prototype, 'save');
var savePost = sinon.stub(Post.prototype, 'save');
saveComment.returnsPromise().resolves({
comment: {/**/}
});
saveUser.returnsPromise().resolves({
user: {/**/}
});
savePost.returnsPromise().resolves({
post: {/**/}
});
// Mocking req and res with node-mocks-http
req = mockHttp.createRequest({
method: 'POST',
url: '/comments',
user: {/**/},
body: {/**/}
});
res = mockHttp.createResponse();
// Call the handler.
commentsController.create_comment(req, res);
expect(saveComment.called).to.equal(true); // Pass
userMock.verify(); // Pass
postMock.verify(); // Pass
expect(saveUser.called).to.equal(true); // Fail
expect(savePost.called).to.equal(true); // Fail
});
As you can see, the calls to user.save()
and post.save()
are not made. This might be a problem with my Promise.all()
setup and subsequent handling or my test itself, but I am out of ideas.
Can you spot my error?
Thanks in advance, guys.
It took me longer to recreate the missing parts of your example, than to actually find the problem. Below you can find a fixed version of your test scenario.
var mongoose = require('mongoose');
module.exports = mongoose.model('User', {
name: String,
comments: Array
});
var mongoose = require('mongoose');
module.exports = mongoose.model('Post', {
name: String,
comments: Array
});
var mongoose = require('mongoose');
module.exports = mongoose.model('Comment', {
text: String
});
var mongoose = require('mongoose');
mongoose.Promise = require('bluebird');
var Comment = require('./comment');
var User = require('./user');
var Post = require('./post');
// Creates a comment.
exports.create_comment = function (req, res) {
var comment = new Comment();
const userId = req.user.id;
const postId = req.body.id;
return comment.save().then(function (createdComment) {
res.json(createdComment);
var promises = [
User.findById(userId).exec(),
Post.findById(postId).exec()
];
// Should resolve to [{ user: {/* */} }, { post: {/* */} }]
var promisedDocs = Promise.all(promises);
// Function provided to Array.map().
function pushAndSave (doc) {
doc.comments.push(createdComment);
return doc.save();
}
// Return promise and process the docs returned by resolved promise.
return promisedDocs.then(function (results) {
results.map(pushAndSave);
});
})
.catch(function (err) {
console.error('foo', err);
res.json(err);
});
};
'use strict';
const chai = require('chai');
const sinon = require('sinon');
const SinonChai = require('sinon-chai');
var sinonStubPromise = require('sinon-stub-promise');
sinonStubPromise(sinon);
require('sinon-mongoose');
chai.use(SinonChai);
const expect = chai.expect;
var mockHttp = require('node-mocks-http');
const commentsController = require('./controller');
var Comment = require('./comment');
var User = require('./user');
var Post = require('./post');
describe.only('Test', () => {
it('Makes all the appropriate calls when everyting goes right',
function (done) {
// Mock through sinon-mongoose
var userMock = sinon.mock(User);
var postMock = sinon.mock(Post);
userMock
.expects('findById')
.chain('exec')
.resolves(new User());
postMock
.expects('findById')
.chain('exec')
.resolves(new Post());
// Stubbing calls to Model#save.
var saveComment = sinon.stub(Comment.prototype, 'save');
var saveUser = sinon.stub(User.prototype, 'save');
var savePost = sinon.stub(Post.prototype, 'save');
saveComment.resolves({
comment: { /**/}
});
saveUser.resolves({
user: { /**/}
});
savePost.resolves({
post: { /**/}
});
// Mocking req and res with node-mocks-http
const req = mockHttp.createRequest({
method: 'POST',
url: '/comments',
user: {id: '123'},
body: {id: 'xxx'}
});
const res = mockHttp.createResponse();
// Call the handler.
commentsController.create_comment(req, res).then(() => {
expect(saveComment.called).to.equal(true); // Pass
userMock.verify(); // Pass
postMock.verify(); // Pass
expect(saveUser.called).to.equal(true); // Fail
expect(savePost.called).to.equal(true); // Fail
done();
});
});
});
Overall, the assertion logic was ok in theory. The problems that I found were the following: