Mocking/stubbing Mongoose model save method Mocking/stubbing Mongoose model save method mongoose mongoose

Mocking/stubbing Mongoose model save method


The basics

In unit testing one should not hit the DB. I could think of one exception: hitting an in-memory DB, but even that lies already in the area of integration testing as you would only need the state saved in memory for complex processes (and thus not really units of functionality). So, yes no actual DB.

What you want to test in unit tests is that your business logic results in correct API calls at the interface between your application and the DB. You can and probably should assume that the DB API/driver developers have done a good job testing that everything below the API behaves as expected. However, you also want to cover in your tests how your business logic reacts to different valid API results such as successful saves, failures due to data consistency, failures due to connection issues etc.

This means that what you need and want to mock is everything that is below the DB driver interface. You would, however, need to model that behaviour so that your business logic can be tested for all outcomes of the DB calls.

Easier said than done because this means you need to have access to the API via the technology you use and you need to know the API.

The reality of mongoose

Sticking to the basics we want to mock the calls performed by the underlying 'driver' that mongoose uses. Assuming it is node-mongodb-native we need to mock out those calls. Understanding the full interplay between mongoose and the native driver is not easy, but it generally comes down to the methods in mongoose.Collection because the latter extends mongoldb.Collection and does not reimplement methods like insert. If we are able to control the behaviour of insert in this particular case, then we know we mocked out the DB access at the API level. You can trace it in the source of both projects, that Collection.insert is really the native driver method.

For your particular example I created a public Git repository with a complete package, but I will post all of the elements here in the answer.

The solution

Personally I find the "recommended" way of working with mongoose quite unusable: models are usually created in the modules where the corresponding schemas are defined, yet they already need a connection. For purposes of having multiple connections to talk to completely different mongodb databases in the same project and for testing purposes this makes life really hard. In fact, as soon as concerns are fully separated mongoose, at least to me, becomes nearly unusable.

So the first thing I create is the package description file, a module with a schema and a generic "model generator":

package.json

{  "name": "xxx",  "version": "0.1.0",  "private": true,  "main": "./src",  "scripts": {    "test" : "mocha --recursive"  },  "dependencies": {    "mongoose": "*"  },  "devDependencies": {    "mocha": "*",    "chai": "*"  }}

src/post.js

var mongoose = require("mongoose");var PostSchema = new mongoose.Schema({    title: { type: String },    postDate: { type: Date, default: Date.now }}, {    timestamps: true});module.exports = PostSchema;

src/index.js

var model = function(conn, schema, name) {    var res = conn.models[name];    return res || conn.model.bind(conn)(name, schema);};module.exports = {    PostSchema: require("./post"),    model: model};

Such a model generator has its drawbacks: there are elements that may need to be attached to the model and it would make sense to place them in the same module where the schema is created. So finding a generic way to add those is a bit tricky. For example, a module could export post-actions to be automatically run when a model is generated for a given connection etc. (hacking).

Now let's mock the API. I'll keep it simple and will only mock what I need for the tests in question. It is essential that I would like to mock out the API in general, not individual methods of individual instances. The latter might be useful in some cases, or when nothing else helps, but I would need to have access to objects created inside of my business logic (unless injected or provided via some factory pattern), and this would mean modifying the main source. At the same time, mocking the API in one place has a drawback: it is a generic solution, which would probably implement successful execution. For testing error cases, mocking in instances in the tests themselves could be required, but then within your business logic you might not have direct access to the instance of e.g. post created deep inside.

So, let's have a look at the general case of mocking successful API call:

test/mock.js

var mongoose = require("mongoose");// this method is propagated from node-mongodb-nativemongoose.Collection.prototype.insert = function(docs, options, callback) {    // this is what the API would do if the save succeeds!    callback(null, docs);};module.exports = mongoose;

Generally, as long as models are created after modifying mongoose, it is thinkable that the above mocks are done on per test basis to simulate any behaviour. Make sure to revert to the original behaviour, however, before every test!

Finally this is how our tests for all possible data saving operations could look like. Pay attention, these are not specific to our Post model and could be done for all other models with exactly the same mock in place.

test/test_model.js

// now we have mongoose with the mocked API// but it is essential that our models are created AFTER // the API was mocked, not in the main source!var mongoose = require("./mock"),    assert = require("assert");var underTest = require("../src");describe("Post", function() {    var Post;    beforeEach(function(done) {        var conn = mongoose.createConnection();        Post = underTest.model(conn, underTest.PostSchema, "Post");        done();    });    it("given valid data post.save returns saved document", function(done) {        var post = new Post({            title: 'My test post',            postDate: Date.now()        });        post.save(function(err, doc) {            assert.deepEqual(doc, post);            done(err);        });    });    it("given valid data Post.create returns saved documents", function(done) {        var post = new Post({            title: 'My test post',            postDate: 876543        });        var posts = [ post ];        Post.create(posts, function(err, docs) {            try {                assert.equal(1, docs.length);                var doc = docs[0];                assert.equal(post.title, doc.title);                assert.equal(post.date, doc.date);                assert.ok(doc._id);                assert.ok(doc.createdAt);                assert.ok(doc.updatedAt);            } catch (ex) {                err = ex;            }            done(err);        });    });    it("Post.create filters out invalid data", function(done) {        var post = new Post({            foo: 'Some foo string',            postDate: 876543        });        var posts = [ post ];        Post.create(posts, function(err, docs) {            try {                assert.equal(1, docs.length);                var doc = docs[0];                assert.equal(undefined, doc.title);                assert.equal(undefined, doc.foo);                assert.equal(post.date, doc.date);                assert.ok(doc._id);                assert.ok(doc.createdAt);                assert.ok(doc.updatedAt);            } catch (ex) {                err = ex;            }            done(err);        });    });});

It is essential to note that we are still testing the very low level functionality, but we can use this same approach for testing any business logic that uses Post.create or post.save internally.

The very final bit, let's run the tests:

~/source/web/xxx $ npm test

> xxx@0.1.0 test /Users/osklyar/source/web/xxx> mocha --recursivePost  ✓ given valid data post.save returns saved document  ✓ given valid data Post.create returns saved documents  ✓ Post.create filters out invalid data3 passing (52ms)

I must say, this is no fun to do it that way. But this way it is really pure unit-testing of the business logic without any in-memory or real DBs and fairly generic.


If what you want is test static's and method's of certain Mongoose model, I would recommend you to use sinon and sinon-mongoose. (I guess it's compatible with chai)

This way, you won't need to connect to Mongo DB.

Following your example, suppose you have a static method findLast

//If you are using callbacksPostSchema.static('findLast', function (n, callback) {  this.find().limit(n).sort('-postDate').exec(callback);});//If you are using PromisesPostSchema.static('findLast', function (n) {  this.find().limit(n).sort('-postDate').exec();});

Then, to test this method

var Post = mongoose.model('Post');// If you are using callbacks, use yields so your callback will be calledsinon.mock(Post)  .expects('find')  .chain('limit').withArgs(10)  .chain('sort').withArgs('-postDate')  .chain('exec')  .yields(null, 'SUCCESS!');Post.findLast(10, function (err, res) {  assert(res, 'SUCCESS!');});// If you are using Promises, use 'resolves' (using sinon-as-promised npm) sinon.mock(Post)  .expects('find')  .chain('limit').withArgs(10)  .chain('sort').withArgs('-postDate')  .chain('exec')  .resolves('SUCCESS!');Post.findLast(10).then(function (res) {  assert(res, 'SUCCESS!');});

You can find working (and simple) examples on the sinon-mongoose repo.