Testing mongoose pre-save hook

Viewed 3478

I am quite new to testing nodejs. So my approach might be completely wrong. I try to test a mongoose models pre-save-hook without hitting the Database. Here is my model:

// models/user.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;

UserSchema = new Schema({
    email: {type: String, required: true},
    password: {type: String, required: true}    
});

UserSchema.pre('save', function (next) {
    const user = this;
    user.email = user.email.toLowerCase();
    // for testing purposes
    console.log("Pre save hook called");
    next();
});

module.exports = mongoose.model("User", UserSchema);

As I said, I do not want to hit the Database with my test, so I tried using a sinon stub of the Users save() method:

// test/models/user.js
const sinon = require("sinon");
const chai = require("chai");
const assert = chai.assert;

const User = require("../../models/user");

describe("User", function(){
    it("should convert email to lower case before saving", (done) => {
        const user = new User({email: "Valid@Email.com", password: "password123"});
        const saveStub = sinon.stub(user, 'save').callsFake(function(cb){ cb(null,this) })

        user.save((err,res) => {
            if (err) return done(err);
            assert.equal(res.email,"valid@email.com");
            done();
        })
    })
});

However, If I do it like that the pre-save hook will not be called. Am I on the wrong path or am I missing something? Or is there maybe another way of triggering the pre-save hook and testing its outcome? Thanks very much in advance!

2 Answers

I just faced the same issue and managed to solve it by extracting the logic out of the hook, making it possible to test it in isolation. With isolation I mean without testing anything Mongoose related.

You can do so by creating a function, that enforces your logic, with the following structure:

function preSaveFunc(next, obj) {
  // your logic
  next();
}

You can then call it in your hook:

mySchema.pre('save', function (next) { preSaveFunc(next, this); });

This will make the reference to this available inside the function, so you can work with it.

The extracted part can then be unit tested by overwriting the next function to a function without a body.

Hope this will help anyone as it actually was a pain to solve this with my limited knowledge on Mongoose.

Related