(1 minute read)
The other day, whilst writing tests with SinonJS I realised that there was no obvious way of mocking calls to class constructors in Javascript. A quick search for "mocking Javascript class constructor" did lead me to some helpful answers.
Generally speaking, you can only mock a method which exists against an object. So in order to mock the MyClass constructor you have to mock the MyClass method on its container object:
var sinon = require('sinon');
exports.MyClass = function() {
this.a = 1;
};
var spy = sinon.spy(exports, 'MyClass');
var inst = new exports.MyClass();
console.log(spy.calledOnce); // true
The above example is for Node. In the browser the global object to which top-level functions automatically belong is window. Note though what happens if you add a local variable reference into the mix:
var sinon = require('sinon');
var MyClass = exports.MyClass = function() {
this.a = 1;
};
var spy = sinon.spy(exports, 'MyClass');
var inst = new MyClass();
console.log(spy.calledOnce); // false
This discrepancy occurs because Sinon wraps exports.MyClass with its own mechanism, which means that the MyClass local variable which points directly to the constructor remains unaffected. To prove the point:
var sinon = require('sinon');
exports.MyClass = function() {
this.a = 1;
};
var spy = sinon.spy(exports, 'MyClass');
var MyClass = exports.MyClass;
var inst = new MyClass();
console.log(spy.calledOnce); // true
If you are calling a base class constructor from within a subclass you will generally be writing something like this:
var util = require('util'); // core node.js module
var Controller = function() {};
var DefaultController = function() {
Controller.apply(this, Array.prototype.slice.call(arguments, 0));
};
util.inherits(DefaultController, Controller);
In such instances you can mock as previously noted. Alternatively you can mock the call to apply():
var sinon = require('sinon');
var util = require('util'); // core node.js module
var Controller = function() {};
var DefaultController = function() {
Controller.apply(this, Array.prototype.slice.call(arguments, 0));
};
util.inherits(DefaultController, Controller);
var spy = sinon.spy(Controller, 'apply');
var inst = new DefaultController();
console.log(spy.calledOnce); // true