Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 811
Theo Sherry Avatar asked Nov 13 '15 22:11

Theo Sherry


People also ask

How do you mock a constructor in Jest?

In order to mock a constructor function, the module factory must return a constructor function. In other words, the module factory must be a function that returns a function - a higher-order function (HOF). Since calls to jest. mock() are hoisted to the top of the file, Jest prevents access to out-of-scope variables.

How do you mock in node JS?

In Jest, Node. js modules are automatically mocked in your tests when you place the mock files in a __mocks__ folder that's next to the node_modules folder. For example, if you a file called __mock__/fs. js , then every time the fs module is called in your test, Jest will automatically use the mocks.

What is constructor in Nodejs?

The constructor method is a special method of a class for creating and initializing an object instance of that class. Note: This page introduces the constructor syntax. For the constructor property present on all objects, see Object. prototype.

What is function mockconstructor Jest?

Mock functions allow you to test the links between code by erasing the actual implementation of a function, capturing calls to the function (and the parameters passed in those calls), capturing instances of constructor functions when instantiated with new , and allowing test-time configuration of return values.


2 Answers

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

like image 141
Jan Molak Avatar answered Oct 15 '22 07:10

Jan Molak


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.

like image 24
Jannes Botis Avatar answered Oct 15 '22 07:10

Jannes Botis