Stub save Instance Method of Mongoose Model With Sinon Stub save Instance Method of Mongoose Model With Sinon mongoose mongoose

Stub save Instance Method of Mongoose Model With Sinon


If were to test that, this is how I would approach it, first have a way to inject my mocked widget to the widget-service. I know there's node-hijack, mockery or something like node-di, they all have different styles, I'm sure there's more. Choose one and use it.

Once I get that right, then I create my widget-service with my mock widget module. Then I do something like this(this is using 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();    });});

This is just a sample, my tests sometimes get more complicated. It happens that most of the time, the backend calling function(here it is, widget.save) that I want to mock, is the one that I want it's behavior to change with every different test, so that's why I reset the stub everytime.

Here's also another example for doing similar thing: https://github.com/mozilla-b2g/gaia/blob/16b7f7c8d313917517ec834dbda05db117ec141c/apps/sms/test/unit/thread_ui_test.js#L1614


Here is how I would do it. I'm using Mockery to manipulate the module loading. The code of widgetservice.js must changed so that it calls require('./widget');, without the .js extension. Without the modification, the following code won't work because I use the general recommended practice of avoiding extensions in require calls. Mockery is states clearly that the names passed to the require call must match exactly so.

The test runner is Mocha.

The code follows. I've put copious comments in the code itself.

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

Unless I've missed something, this covers all the tests that were mentioned in the question.


With current version of Mongoose you can use create method

// widgetservice.jsvar Widget = require('./widget.js');var createWidget = function(data, callback) {  Widget.create(data, callback);};

Then to test the method (using Mocha)

// test.jsvar 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();      });    });  });});

Also, if you want to mock chained methods use sinon-mongoose.