Mocking constructor functions in node
I think it's worth asking why you'd want to mock a constructor of a dependency instead of injecting that dependency?
Consider your example code:
// in "foo.js"
function foo() {
var dependency = new Dependency(args);
// do stuff with dependency
}
exports.module.foo = foo;
If Dependency
is required for foo
to work you can inject it as an argument of foo
:
// in "foo.js"
function foo(dependency) {
// do stuff with dependency
}
exports.module.foo = foo;
// in "bar.js"
var foo = require('./foo.js')(new Dependency(args));
With this change it's now trivial to inject any Test Double in your tests (to find out more about JavaScript Test Doubles have a look at my article on the subject).
This approach makes the dependencies of your function/module explicit, but requires you to wire them up at some point (here: require('./foo.js')(new Dependency(args));
).
If you didn't want to wire things up manually there's another approach you can take using rewire and replacing constructor with factory method:
// in "dependency.js"
module.exports= function(args) {
return new Dependency(args);
}
// in "foo.js"
var dependency = require('./dependency');
function foo() {
var dep = dependency(args);
// do stuff with dependency
}
exports.module.foo = foo;
and in your test:
var rewire = require("rewire"),
foo = rewire("../lib/foo.js");
it('should call dependency... ', function() {
foo.__set__("dependency", /* some spy */ );
foo();
});
Hope this helps!
Jan
I used a workaround for this:
// Keep a reference to your dependancy class.
const dependencyClass = Dependency;
let item = new Dependency();
// Replace class with a stub.(optionally return an item.
Dependency = sinon.stub(Dependency, 'constructor').returns(item);
// Call your code that you expect the class constructor to be called.
foo();
assert.isTrue(Dependency.calledWithNew());
assert.equal(1, Dependency.callCount);
// Restore class reference.
Dependency = dependencyClass;
Additionally, in the above case, an item is returned, so the user can have access to the dependency for further testing. eg.
assert.equal(item.someValue, 10);
You can have other problems using this, eg defined properties will no longer be available for the class. I agree with Jan Molek, use this in case you cannot change the code.