27

I want to create a stub for the Mongoose save method in a particular model, so that any instance of my model I create will call the stub instead of the normal Mongoose save method. My understanding is that the only way to do this is to stub the entire model like this:

var stub = sinon.stub(myModel.prototype);

Unfortunately, this line of code causes my tests to throw the following error:

TypeError: Cannot read property 'states' of undefined

Does anyone know what is going wrong here?

theUtherSide
  • 3,338
  • 4
  • 36
  • 35
amandawulf
  • 673
  • 3
  • 9
  • 18

6 Answers6

32

There are two ways to accomplish this. The first is

var mongoose = require('mongoose');
var myStub = sinon.stub(mongoose.Model, METHODNAME);

If you console log mongoose.Model you will see the methods available to the model (notably this does not include lte option).

The other (model specific) way is

var myStub = sinon.stub(YOURMODEL.prototype.base.Model, 'METHODNAME');

Again, the same methods are available to stub.

EDIT: Some methods such as save are stubbed as follows:

var myStub = sinon.stub(mongoose.Model.prototype, METHODNAME);
var myStub = sinon.stub(YOURMODEL.prototype, METHODNAME);
Jacob
  • 356
  • 2
  • 6
  • How would that work for chained queries? Something like `MYMODEL.findOne(...).where(...).gt(...).exec(callback)` – Nepoxx Jan 08 '15 at 18:40
  • @Milovan Zogovic see my answer below – djv Jan 07 '16 at 22:55
  • For Static Methods: `var myStub = sinon.stub(MYMODEL.schema.statics, STATICNAME);` if anyone's interested. Great answer, @Jacob -- console.log suggestion helped me a lot – lonesomewhistle Apr 30 '16 at 18:51
  • 1
    Worth noting that the first option for stubbing `save()` (stubbing the generic `Model` prototype), doesn't appear to work on mongoose anymore (mongoose version 4.11). Stubbing prototype of the custom model still appears to work. – JoeWemyss Aug 29 '17 at 18:29
8

Take a look to sinon-mongoose. You can expects chained methods with just a few lines:

sinon.mock(YourModel).expects('find')
  .chain('limit').withArgs(10)
  .chain('exec');

You can find working examples on the repo.

Also, a recommendation: use mock method instead of stub, that will check the method really exists.

Gon
  • 627
  • 7
  • 6
  • 3
    Does this depend on how the code is written, and not what the code is doing? The test should be valid whether the calls are chained or not in the code under test. – CLo Nov 19 '15 at 16:20
  • Should work. But if don't, please post an example :) – Gon Nov 23 '15 at 14:26
  • This does not work on the save method. And that's the method asked to mock by the poster. – SomeDutchGuy Nov 12 '19 at 11:35
7

save is not a method on the model, it's a method on the document (instance of a model). Stated here in mongoose docs.

Constructing documents

Documents are instances of our model. Creating them and saving to the database is easy

Therefore, it will always be undefined if you're using your model to mock a save()

Going along with @Gon's answer, using sinon-mongoose & factory-girl with Account being my model:

Will not work

var AccountMock = sinon.mock(Account)

AccountMock
  .expects('save') // TypeError: Attempted to wrap undefined property save as function
  .resolves(account)

Will work

var account = { email: 'sasha@gmail.com', password: 'abc123' }

Factory.define(account, Account)
Factory.build('account', account).then(accountDocument => {
  account = accountDocument

  var accountMock = sinon.mock(account)

  accountMock
    .expects('save')
    .resolves(account)

  // do your testing...
})
djv
  • 466
  • 6
  • 15
2

Instead of the whole object, try:

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

Make sure YOURMODEL is the class not the instance.

Tanzeeb Khalili
  • 7,324
  • 2
  • 23
  • 27
2

Tangentially-related, but relevant...

I needed to mock a custom model method like:

myModelSchema.methods.myCustomMethod = function() {....}

To create a stub I did:

myCustomMethodStub = sinon.stub(MyModel.schema.methods, 'myCustomMethod').callThrough();
theUtherSide
  • 3,338
  • 4
  • 36
  • 35
1

As told by djv, the save method is on the document. So you can stub it this way:

const user = new User({
      email: 'email@email.com',
      firstName: 'firstName',
      lastName: 'lastName',
      userName: 'userName',
      password: 'password',
    });

stub(user, 'save').resolves({ foo: 'bar' });

Bonus, you can assert it with Chai and Chai as promised this way:

const promise = user.save();
await chai.assert.doesNotBecome(promise, { foo: 'bar' });
Ernest Jones
  • 564
  • 6
  • 20