【问题标题】:Stubbing the mongoose save method on a model在模型上存根猫鼬保存方法
【发布时间】:2015-05-03 16:10:55
【问题描述】:

我想对 Mongoose 模型可用的 save 方法存根。这是一个示例模型:

/* model.js */
var mongoose = require('mongoose');
var userSchema = mongoose.Schema({
  username: {
    type: String,
    required: true
  }
});
var User = mongoose.model('User', userSchema);
module.exports = User;

我有一些辅助函数会调用save 方法。

/* utils.js */
var User = require('./model');
module.exports = function(req, res) {
  var username = req.body.username;
  var user = new User({ username: username });
  user.save(function(err) {
    if (err) return res.end();
    return res.sendStatus(201);
  });
};

我想检查 user.save 是否在我的辅助函数中使用单元测试被调用。

/* test.js */
var mongoose = require('mongoose');
var createUser = require('./utils');
var userModel = require('./model');

it('should do what...', function(done) {
  var req = { username: 'Andrew' };
  var res = { sendStatus: sinon.stub() };
  var saveStub = sinon.stub(mongoose.Model.prototype, 'save');
  saveStub.yields(null);

  createUser(req, res);

  // because `save` is asynchronous, it has proven necessary to place the
  // expectations inside a setTimeout to run in the next turn of the event loop
  setTimeout(function() {
    expect(saveStub.called).to.equal(true);
    expect(res.sendStatus.called).to.equal(true);
    done();
  }, 0)
});

我从here 发现了var saveStub = sinon.stub(mongoose.Model.prototype, 'save')

除非我尝试向我的 saveStub 添加一些内容,否则一切都很好,例如与saveStub.yields(null)。如果我想用saveStub.yields('mock error') 模拟传递给save 回调的错误,我会收到以下错误:

TypeError: Attempted to wrap undefined property undefined as function

堆栈跟踪完全没有帮助。

我所做的研究

按照here 的建议,我尝试重构我的模型以访问底层用户模型。这对我产生了同样的错误。这是我尝试的代码:

/* in model.js... */
var UserSchema = mongoose.model('User');
User._model = new UserSchema();

/* in test.js... */
var saveStub = sinon.stub(userModel._model, 'save');

我发现this solution 根本不适合我。也许这是因为我以不同的方式设置我的用户模型?

我还尝试了在 this guidethis one 之后的 Mockery,但这比我认为的需要的设置要多得多,这让我质疑花时间隔离数据库的价值。

我的印象是,这一切都与猫鼬实现save 的神秘方式有关。我已经使用 npm hooks 阅读了一些关于它的内容,这使得 save 方法很容易存根。

我也听说过mockgoose,虽然我还没有尝试过那个解决方案。有人用这个策略成功了吗? [编辑:事实证明 mockgoose 提供了一个内存数据库以便于设置/拆卸,但它并没有解决存根问题。]

任何有关如何解决此问题的见解将不胜感激。

【问题讨论】:

    标签: unit-testing mongoose mocha.js


    【解决方案1】:

    这是我开发的最终配置,它使用了 sinon 和 mockery 的组合:

    // Dependencies
    var expect = require('chai').expect;
    var sinon = require('sinon');
    var mockery = require('mockery');
    var reloadStub = require('../../../spec/utils/reloadStub');
    
    describe('UNIT: userController.js', function() {
    
      var reportErrorStub;
      var controller;
      var userModel;
    
      before(function() {
        // mock the error reporter
        mockery.enable({
          warnOnReplace: false,
          warnOnUnregistered: false,
          useCleanCache: true
        });
    
        // load controller and model
        controller = require('./userController');
        userModel = require('./userModel');
      });
    
      after(function() {
        // disable mock after tests complete
        mockery.disable();
      });
    
      describe('#createUser', function() {
        var req;
        var res;
        var status;
        var end;
        var json;
    
        // Stub `#save` for all these tests
        before(function() {
          sinon.stub(userModel.prototype, 'save');
        });
    
        // Stub out req and res
        beforeEach(function() {
          req = {
            body: {
              username: 'Andrew',
              userID: 1
            }
          };
    
          status = sinon.stub();
          end = sinon.stub();
          json = sinon.stub();
    
          res = { status: status.returns({ end: end, json: json }) };
        });
    
        // Reset call count after each test
        afterEach(function() {
          userModel.prototype.save.reset();
        });
    
        // Restore after all tests finish
        after(function() {
          userModel.prototype.save.restore();
        });
    
        it('should call `User.save`', function(done) {
          controller.createUser(req, res);
          /**
           * Since Mongoose's `new` is asynchronous, run our expectations on the
           * next cycle of the event loop.
           */
          setTimeout(function() {
            expect(userModel.prototype.save.callCount).to.equal(1);
            done();
          }, 0);
        });
      }
    }
    

    【讨论】:

      【解决方案2】:

      你试过了吗:

      sinon.stub(userModel.prototype, 'save')
      

      另外,辅助函数在哪里被调用?看起来您将函数定义为 utils 模块,但将其作为控制器对象的方法调用。我假设这与该错误消息无关,但它确实使我们更难弄清楚存根被调用的时间和地点。

      【讨论】:

      • 感谢您的回复。谢谢你的收获。将函数定义为模块和稍后将其作为方法调用的区别是在清理代码并为 SO 简化代码时引入的错误。我现在已经更正了。
      • 我确实尝试过使用sinon.stub(userModel.prototype, 'save')。它确实准确地捕获了save 方法上的行为,但它仍然抛出了我之前描述的TypeError
      猜你喜欢
      • 2016-01-21
      • 2020-09-05
      • 1970-01-01
      • 2016-06-29
      • 2015-01-14
      • 1970-01-01
      • 1970-01-01
      • 2017-05-05
      相关资源
      最近更新 更多