Stub save Instance Mongoose model method with Sinon

I am trying to test a service function that I use to save a widget using the Mongoose model. I want to stub the save instance method on my model, but I cannot find a suitable solution. I saw other offers, but no one seems complete.

See ... this and.

Here is my model ...

// widget.js var mongoose = require('mongoose'); var widgetSchema = mongoose.Schema({ title: {type: String, default: ''} }); var Widget = mongoose.model('Widget', widgetSchema); module.exports = Widget; 

Here is my service ...

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

My service is very simple. It takes some JSON data, creates a new widget, and then saves the widget using the "save" instance method. It then calls the postback err and doc based on the result of the save call.

I want to check this only when calling createWidget ({title: 'Widget A'}) ...

  • The Widget constructor is called once with the data of the service function passed to me
  • The save instance method for the newly created widget object is called once
  • EXTRA CREDIT: The save instance method calls the null callback for err and {title: 'Widget A'} for the document.

To test this separately, I probably need ...

  • Check or close the Widget constructor so that it returns a mock object of the object that I create as part of my test.
  • Set up the save function of the widget object so that I can control what happens.

I find it difficult to understand how to do this with Sinon. I tried several options found on SO pages with no luck.

NOTES:

  • I don’t want to pass the already built model object to the service, because I want the service to be the only thing that “knows” about mongoose.
  • I know this is not the biggest deal (just checking it with most of the integration or the end-to-end test, but it would be nice to find a solution.

Thanks for any help you can provide.

+8
javascript unit-testing mongoose sinon
source share
3 answers

If it were necessary to check, so I would come closer to this, first try to breathe my widget into the widget service. I know there node-hijack , mockery or something like node-di , they all have different styles, I'm sure there are more of them. Select it and use.

As soon as I get this right, I create my widget service with my widget module. Then I do something like this (this is used by 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 complicated. It happens that most of the time the backend call function (here it is, widget.save) that I want to make fun of is the one I want the behavior to change with every other test, so why do I reset the stub every time.

Here is another example to accomplish a similar thing: https://github.com/mozilla-b2g/gaia/blob/16b7f7c8d313917517ec834dbda05db117ec141c/apps/sms/test/unit/thread_ui_test.js#L1614

+5
source share

This is how I do it. I use Mockery to control module loading. The widgetservice.js code must be modified so that it calls require('./widget'); without extension .js . Without change, the following code will not work, because I use the general recommended practice of excluding extensions in require calls. Mockery clearly states that the names passed to the require call must match exactly that.

Test runner Mocha .

The following is the code. I wrote a lot of 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(); }); }); }); 

If I missed something, this covers all the tests that were mentioned in the question.

+3
source share

In the current version of Mongoose, you can use the create method

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

Then, to test the method (using 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(); }); }); }); }); 

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

+3
source share

All Articles