Theo Sherry
Theo Sherry

Reputation: 123

Mocking constructor functions in node

How do other node developers who use sinon mock out constructor calls within their unit tests? For example, suppose I have some function foo

function foo() {
  var dependency = new Dependency(args);
  // do stuff with dependency
}
exports.module.foo = foo;

and in a separate test file I have some test in which I want to verify what the Dependency constructor is called with (args), and I need to control what it returns

it('should call Dependency constructor with bar', function() {
  var foo = require('myModule').foo
  var DependencyMock; //code to make the mock

  foo();
  expect(DependencyMock.calledWith(bar)).to.equal(true);
});

The problem is that sinon can only mock functions attached to an object, so we have to attach the constructor to an object before it can be mocked.

What I've been doing is just making an object to attach the constructor to in the module making the constructor call, calling the constructor as a method of that object, then exporting the object to use it in tests:

var Dependency = require('path/to/dependency');

var namespace = {
  Dependency: Dependency
}

function foo() {
  var dependency = new namespace.Dependency(args);
  // do stuff with dependency
}
exports.moduole.foo = foo;
exports.module.namespace = namespace;

testfile:

it('should call Dependency constructor with bar', function() {
  var foo = require('myModule').foo;
  var namespace = require('myModule').namespace;

  var DependencyMock = sinon.mock(namespace, 'Dependency').returns(0);
  foo();
  expect(DependencyMock.calledWith(bar)).to.equal(true);
});

This works, but it feels really clunky to expose an object on my module just for the sake of testing it.

Any tips?

Upvotes: 10

Views: 14538

Answers (4)

Jan Molak
Jan Molak

Reputation: 4526

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

Upvotes: 9

Jannes Botis
Jannes Botis

Reputation: 11242

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.

Upvotes: 5

Ben Davies
Ben Davies

Reputation: 647

I encountered a similar problem with modules that expose functions rather than objects - this was my solution. Just putting this here as another way to go about tackling the issue but I must say solving the problem via better design, as Jan Molak suggested, seems like a better approach.

Upvotes: 0

Tom
Tom

Reputation: 133

Not tried, but this could work: stub the constructor function of Dependency and let it return a mock.

var constructor = sinon.stub(Dependency.prototype, "constructor");
constructor.returns(sinon.mock(Dependency));

Upvotes: 0

Related Questions