我正在尝试使用Mongoose模型测试我用来保存小部件的服务功能。我想在我的模型上存根保存实例方法,但我无法找到一个好的解决方案。我看到了其他建议,但似乎都没有完整。
这是我的模特......
// widget.js
var mongoose = require('mongoose');
var widgetSchema = mongoose.Schema({
title: {type: String, default: ''}
});
var Widget = mongoose.model('Widget', widgetSchema);
module.exports = Widget;
这是我的服务......
// widgetservice.js
var Widget = require('./widget.js');
var createWidget = function(data, callback) {
var widget = new Widget(data);
widget.save(function(err, doc) {
callback(err, doc);
});
};
我的服务很简单。它接受一些JSON数据,创建一个新的小部件,然后使用“save”实例方法保存小部件。然后它根据保存调用的结果回调传递错误和文档。
当我调用createWidget({title:'Widget A'})时,我只想测试...
为了单独测试,我可能需要......
我无法弄清楚如何用Sinon做到这一点。我已经尝试过在SO页面上找到的几个变种而没有运气。
注意:
感谢您提供的任何帮助。
答案 0 :(得分:5)
如果要测试它,这就是我接近它的方法,首先要有一种方法将我的模拟小部件注入到widget-service中。我知道那里有node-hijack,mockery或类似node-di之类的东西,它们都有不同的风格,我相信还有更多。选择一个并使用它。
一旦我做到了,我就用我的模拟小部件模块创建我的小部件服务。然后我做这样的事情(这是使用mocha btw):
// Either do this:
saveStub = sinon.stub();
function WidgetMock(data) {
// some mocking stuff
// ...
// Now add my mocked stub.
this.save = saveStub;
}
// or do this:
WidgetMock = require('./mocked-widget');
var saveStub = sinon.stub(WidgetMock.prototype, 'save');
diInject('widget', WidgetMock); // This function doesn't really exists, but it should
// inject your mocked module instead of real one.
beforeEach(function () {
saveStub.reset(); // we do this, so everytime, when we can set the stub only for
// that test, and wouldn't clash with other tests. Don't do it, if you want to set
// the stub only one time for all.
});
after(function () {
saveStub.restore();// Generally you don't need this, but I've seen at times, mocked
// objects clashing with other mocked objects. Make sure you do it when your mock
// object maybe mocked somewhere other than this test case.
});
it('createWidget()', function (done) {
saveStub.yields(null, { someProperty : true }); // Tell your stub to do what you want it to do.
createWidget({}, function (err, result) {
assert(!err);
assert(result.someProperty);
sinon.assert.called(saveStub); // Maybe do something more complicated. You can
// also use sinon.mock instead of stubs if you wanna assert it.
done();
});
});
it('createWidget(badInput)', function (done) {
saveStub.yields(new Error('shhoo'));
createWidget({}, function (err, result) {
assert(err);
done();
});
});
这只是一个例子,我的测试有时会变得更复杂。它发生在大多数情况下,我想要模拟的后端调用函数(这里是widget.save)是我希望它的行为随着每个不同的测试而改变的,所以&#39 ;为什么我每次都重置存根。
这里也是做类似事情的另一个例子:https://github.com/mozilla-b2g/gaia/blob/16b7f7c8d313917517ec834dbda05db117ec141c/apps/sms/test/unit/thread_ui_test.js#L1614
答案 1 :(得分:3)
我将如何做到这一点。我正在使用Mockery来操纵模块加载。必须更改widgetservice.js
的代码,以便在没有require('./widget');
扩展名的情况下调用.js
。如果没有修改,以下代码将无效,因为我使用了避免require
调用中的扩展的一般建议做法。 Mockery明确指出传递给require
调用的名称必须完全匹配。
测试运动员是Mocha。
代码如下。我在代码本身中添加了大量的注释。
var mockery = require("mockery");
var sinon = require("sinon");
// We grab a reference to the pristine Widget, to be used later.
var Widget = require("./widget");
// Convenience object to group the options we use for mockery.
var mockery_options = {
// `useCleanCache` ensures that "./widget", which we've
// previously loaded is forgotten when we enable mockery.
useCleanCache: true,
// Please look at the documentation on these two options. I've
// turned them off but by default they are on and they may help
// with creating a test suite.
warnOnReplace: false,
warnOnUnregistered: false
};
describe("widgetservice", function () {
describe("createWidget", function () {
var test_doc = {title: "foo"};
it("creates a widget with the correct data", function () {
// Create a mock that provides the bare minimum. We
// expect it to be called with the value of `test_doc`.
// And it returns an object which has a fake `save` method
// that does nothing. This is *just enough* for *this*
// test.
var mock = sinon.mock().withArgs(test_doc)
.returns({"save": function () {}});
// Register our mock with mockery.
mockery.registerMock('./widget', mock);
// Then tell mockery to intercept module loading.
mockery.enable(mockery_options);
// Now we load the service and mockery will give it our mock
// Widget.
var service = require("./widgetservice");
service.createWidget(test_doc, function () {});
mock.verify(); // Always remember to verify!
});
it("saves a widget with the correct data", function () {
var mock;
// This will intercept object creation requests and return an
// object on which we can check method invocations.
function Intercept() {
// Do the usual thing...
var ret = Widget.apply(this, arguments);
// Mock only on the `save` method. When it is called,
// it should call its first argument with the
// parameters passed to `yields`. This effectively
// simulates what mongoose would do when there is no
// error.
mock = sinon.mock(ret, "save").expects("save")
.yields(null, arguments[0]);
return ret;
}
// See the first test.
mockery.registerMock('./widget', Intercept);
mockery.enable(mockery_options);
var service = require("./widgetservice");
// We use sinon to create a callback for our test. We could
// just as well have passed an anonymous function that contains
// assertions to check the parameters. We expect it to be called
// with `null, test_doc`.
var callback = sinon.mock().withArgs(null, test_doc);
service.createWidget(test_doc, callback);
mock.verify();
callback.verify();
});
afterEach(function () {
// General cleanup after each test.
mockery.disable();
mockery.deregisterAll();
// Make sure we leave nothing behind in the cache.
mockery.resetCache();
});
});
});
除非我遗漏了某些内容,否则这涵盖了问题中提及的所有测试。
答案 2 :(得分:3)
使用当前版本的Mongoose,您可以使用create
方法
// widgetservice.js
var Widget = require('./widget.js');
var createWidget = function(data, callback) {
Widget.create(data, callback);
};
然后测试方法(使用Mocha)
// test.js
var sinon = require('sinon');
var mongoose = require('mongoose');
var Widget = mongoose.model('Widget');
var WidgetMock = sinon.mock(Widget);
var widgetService = require('...');
describe('widgetservice', function () {
describe('createWidget', function () {
it('should create a widget', function () {
var doc = { title: 'foo' };
WidgetMock
.expects('create').withArgs(doc)
.yields(null, 'RESULT');
widgetService.createWidget(doc, function (err, res) {
assert.equal(res, 'RESULT');
WidgetMock.verify();
WidgetMock.restore();
});
});
});
});
另外,如果你想模拟链式方法,请使用sinon-mongoose。