I have a class that leverages helper classes, and I'd like to verify it constructs those objects correctly. So, I'm trying to stub the "constructor" method in my classes, but I'm clearly not doing it right:
"use strict";
class Collaborator {
constructor(settings) {
console.log("Don't want this to be called!")
this.settings = settings;
}
}
class ThingToTest {
constructor(settings) {
this.helper = new Collaborator(settings);
}
}
const assert = require("assert");
const sinon = require("sinon");
describe("ThingToTest", () => {
let settings = "all the things"
context("spying on constructor", () => {
let spy = sinon.spy(Collaborator, "constructor")
after(() => spy.restore())
describe("constructor", () => {
it("creates a Collaborator with provided settings", () => {
new ThingToTest(settings);
sinon.assert.calledWith(spy, settings)
})
})
})
context("spying on prototype constructor", () => {
let spy = sinon.spy(Collaborator.prototype, "constructor")
after(() => spy.restore())
describe("constructor", () => {
it("creates a Collaborator with provided settings", () => {
new ThingToTest(settings);
sinon.assert.calledWith(spy, settings)
})
})
})
context("stub constructor", () => {
before(() => {
sinon.stub(Collaborator, "constructor", (settings) => {
console.log("This should be called so we can inspect", settings);
})
})
after(() => { Collaborator.constructor.restore() })
describe("constructor", () => {
it("creates a Collaborator with provided settings", () => {
new ThingToTest(settings);
})
})
})
context("stub prototype constructor", () => {
before(() => {
sinon.stub(Collaborator.prototype, "constructor", (settings) => {
console.log("This should be called so we can inspect", settings);
})
})
after(() => { Collaborator.prototype.constructor.restore() })
describe("constructor", () => {
it("creates a Collaborator with provided settings", () => {
new ThingToTest(settings);
})
})
})
})
Running this produces these (undesirable) results:
ThingToTest
spying on constructor
constructor
Don't want this to be called!
1) creates a Collaborator with provided settings
spying on prototype constructor
constructor
Don't want this to be called!
2) creates a Collaborator with provided settings
stub constructor
constructor
Don't want this to be called!
✓ creates a Collaborator with provided settings
stub prototype constructor
constructor
Don't want this to be called!
✓ creates a Collaborator with provided settings
It seems like stubbing is sort of working since putting the stub tests before the spy tests errors with the dreaded "TypeError: Attempted to wrap constructor which is already wrapped". So, clearly figuring out how to mock the Collaborators constructor is only half of what I'm doing wrong . . . I'm not restoring the constructor correctly either. Any suggestions?
Not sure this is my final answer, but I ended up using proxyquire, as it's the best solution I've found so far. To show how it works, I've separated the classes under test into their own directory, and the test file in a child "test" directory. This illustrates how the paths in proxyquire work (which took me some time to figure out). So, here's what I ended up with:
/Collaborator.js
/ThingToTest.js
/test/ExampleTest.js
Note how the path inside
proxyquire("../ThingToTest", { "./Collaborator" : mockCollaborator })
matches what "ThingToTest" uses, not the path from the test class. I hope this help others, but I'm still open to other ideas and suggestions!