Stubbing virtual attributes of Mongoose model

Problem

Is there a way to stub a virtual attribute of a Mongoose Model?

Assume Problem is a model class, and difficulty is a virtual attribute. delete Problem.prototype.difficulty returns false, and the attribute is still there, so I can't replace it with any value I want.

I also tried

var p = new Problem();
delete p.difficulty;
p.difficulty = Problem.INT_EASY;

It didn't work.

Assigning undefined to Problem.prototype.difficulty or using sinon.stub(Problem.prototype, 'difficulty').returns(Problem.INT_EASY); would throw an exception "TypeError: Cannot read property 'scope' of undefined", while doing

  var p = new Problem();
  sinon.stub(p, 'difficulty').returns(Problem.INT_EASY);

would throw an error "TypeError: Attempted to wrap string property difficulty as function".

I am running out of ideas. Help me out! Thanks!

Problem courtesy of: Clive

Solution

mongoose internally uses Object.defineProperty for all properties. Since they are defined as non-configurable, you can't delete them, and you can't re-configure them, either.

What you can do, though, is overwriting the model’s get and set methods, which are used to get and set any property:

var p = new Problem();
p.get = function (path, type) {
  if (path === 'difficulty') {
    return Problem.INT_EASY;
  }
  return Problem.prototype.get.apply(this, arguments);
};

Or, a complete example using sinon.js:

var mongoose = require('mongoose');
var sinon = require('sinon');

var problemSchema = new mongoose.Schema({});
problemSchema.virtual('difficulty').get(function () {
  return Problem.INT_HARD;
});

var Problem = mongoose.model('Problem', problemSchema);
Problem.INT_EASY = 1;
Problem.INT_HARD = 2;

var p = new Problem();
console.log(p.difficulty);
sinon.stub(p, 'get').withArgs('difficulty').returns(Problem.INT_EASY);
console.log(p.difficulty);
Solution courtesy of: Adrian Heine

Discussion

There is currently no discussion for this recipe.

This recipe can be found in it's original form on Stack Over Flow.