【问题标题】:Mongoose Model#save not being called in Promise.all() handling在 Promise.all() 处理中未调用 Mongoose Model#save
【发布时间】:2017-10-03 01:13:34
【问题描述】:

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

// 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() 设置和后续处理或我的测试本身的问题,但我没有想法。 你能发现我的错误吗? 提前谢谢各位。

【问题讨论】:

    标签: express mongoose bluebird sinon


    【解决方案1】:

    我花了更长的时间来重新创建示例中缺失的部分,而不是真正找到问题所在。您可以在下面找到测试场景的固定版本。

    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 函数,以便您可以看到我在说什么
    • 您在同步模式下测试了 async 方法,这肯定会导致断言出现问题。我已将测试用例切换为异步模式,方法完成后立即执行断言

    【讨论】:

    • 您说的非常对,先生。昨天我发现了关于 modelMock 没有解析为 mongoose 模型实例的问题,但是第二个问题,在同步模式下测试异步代码是什么让我很生气。如果测试异步代码,请在异步模式下进行测试。知道了。学过的知识。感谢您抽出宝贵时间,伙计。
    猜你喜欢
    • 2017-10-03
    • 2013-09-04
    • 2018-11-17
    • 2017-08-02
    • 2015-12-27
    • 2019-09-26
    • 2019-10-13
    • 2018-04-08
    • 2019-05-10
    相关资源
    最近更新 更多