Mongoose Model#save在Promise.all()处理中没有被调用

时间:2017-05-04 18:08:51

标签: express mongoose bluebird sinon

我正在测试一个使用mongoose的快速路由处理程序。 我的路由处理程序代码如下。

// 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);
    });
}

我试图测试的逻辑是,当一切正常时,会调用相应的函数。基本上,我期待以下内容: 要调用comment.save()User.findById().exec()Post.findById().exec()user.save()post.save()

为了测试这个,我使用的是mocha,chai,sinon,sinon-mongoose,sinon-stub-promise和node-mocks-http。 这是我的测试(我正在考虑设置)。

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
});

如您所见,未调用user.save()post.save()。这可能是我的Promise.all()设置和后续处理或我的测试本身的问题,但我没有想法。 你能发现我的错误吗? 先谢谢你们。

1 个答案:

答案 0 :(得分:2)

重新创建示例中缺少的部分比实际找到问题花了我更长的时间。您可以在下面找到测试方案的固定版本。

user.js的

var mongoose = require('mongoose');

module.exports = mongoose.model('User', {
    name: String,
    comments: Array
});

post.js

var mongoose = require('mongoose');

module.exports = mongoose.model('Post', {
    name: String,
    comments: Array
});

comment.js

var mongoose = require('mongoose');

module.exports = mongoose.model('Comment', {
    text: String
});

controller.js

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);
        });
};

test.js

'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();
            });

        });

});

总的来说,断言逻辑在理论上是可以的。我发现的问题如下:

  • 您的 userMock postMock 返回了一个普通的空对象,而不是Mongoose模型实例。因此,在 pushAndSave 函数中抛出了一个从未捕获过的异常,因为 .save()方法未定义。我简化了 pushAndSave 功能,以便您可以看到我在谈论的内容
  • 您在同步模式下测试了异步方法,这肯定会导致断言出现问题。我已将测试用例切换为异步,并在方法完成后立即执行断言