express - Mongoose Model#save 在 Promise.all() 处理中未被调用

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

最佳答案

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

用户.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
});

评论.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);
        });
};

测试.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();
            });

        });

});

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

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

关于express - Mongoose Model#save 在 Promise.all() 处理中未被调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43789962/

相关文章:

javascript - 带名称的生成器函数

javascript - 如何等待 Bluebird promise 在多个地点定居?

node.js - TypeError : schematype.castForQueryWrapper不是一个函数

docker - Express session 无法通过Docker在Graphql中持久化 session

javascript - Express Render 无法正常工作返回错误 : No default engine was specified and no extension was provided

node.js + express 3 + socket.io = 发送后无法设置 header

node.js - 通过 _id 转换错误手动删除 connect-mongo session

node.js - Mongoose,从模型中删除属性

javascript - promise 的返回值

node.js - 在 Node JS 集群之间划分快速路由