Sinon: Stubbing constructor does not work

Created on 6 Sep 2018  ·  10Comments  ·  Source: sinonjs/sinon

Describe the bug
Doesn't trigger stubbed constructor

To Reproduce
Run the following test, it will give AssertError: expected constructor to be called once but was called 0 times:

  import sinon from 'sinon';

  class FooBar {
    constructor() {
      console.log("hello");
    }

    f() {
      console.log('f');
    }
  }

  describe('Example', () => {
    it('stubs constructor', () => {
      sinon.stub(FooBar.prototype, 'constructor').callsFake();
      new FooBar();
      sinon.assert.calledOnce(FooBar.prototype.constructor); // fails
    });

    it('stubs method', () => {
      sinon.stub(FooBar.prototype, 'f').callsFake();
      const a = new FooBar();
      a.f();
      sinon.assert.calledOnce(FooBar.prototype.f); // passes
    });
  });

Expected behavior
First test passes (stubbed constructor gets called)

Screenshots
If applicable, add screenshots to help explain your problem.

Context (please complete the following information):

  • Library version: 5.0.7
  • Environment: macOS sierra
  • Other libraries you are using: mocha

Most helpful comment

Your thinking is sound, but the implementation uses deprecated elements.

(__proto__'s) existence and exact behavior has only been standardized in the ECMAScript 2015 specification as a legacy feature
Ref MDN

Basically, you shouldn't use it in your code.

There is a reason the _inherits function you posted a snippet from tries to use Object.setPrototypeOf if available, only falling back to using __proto__ in really old browsers. The reason is that it works.

Object.setPrototypeOf(B, sinon.stub().callsFake(function() { console.log('I am Super Stub!'); } ));
new B(); // prints am Super Stub!
Object.getPrototypeOf(B).callCount // 1

See this gist for more. Also this in case you wonder about ES5 inheritance.

All 10 comments

You’re stubbing a reference to the constructor (prototype.constructor). It’s not possible to stub the constructor itself due to language constraints.

You can inject the constructor and then inject a plain stub in your test to verify that it was called with new.

@RenWenshan This issue has appeared multiple times, but it doesn't have anything to do with Sinon in itself, but is simply due to a missing understanding of what the constructor keyword in ES6 refers to (hint: it shares very little with Function#constructor other than the name).

What we as an org can do to improve the situation is to write tutorials and documentation, something that demands a bit of an effort, which is why we have an issue for tackling this (#1121). Until that materializes itself I suggest reading

That knowledge will make it vastly easier to work with your ES6 (and later) code, as you know what is _really_ happening.

@fatso83 thank you so much for explaining this.

Now I see that constructor is a syntax sugar and it's nothing to do with Function.prototype.constructor, therefore stubbing it has no effect.

My original need was to stub the constructor of a class's parent class so I can test whether super was called, e.g.

class Foo {
  constructor() {
    console.log("Foo");
  }
}

class FooBar extends Foo {
  constructor() {
    super();
    console.log("FooBar");
  }
}

Since the underlying implementation of _inherits includes:

    Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;

I think I can stub the __proto__ of Foo? But it doesn't seem to work:

    const stub = sinon.stub(FooBar, '__proto__').callsFake(() => {
      console.log("stub");
    });

    const foo = new FooBar(); // still outputs "Foo\nFooBar"

Your thinking is sound, but the implementation uses deprecated elements.

(__proto__'s) existence and exact behavior has only been standardized in the ECMAScript 2015 specification as a legacy feature
Ref MDN

Basically, you shouldn't use it in your code.

There is a reason the _inherits function you posted a snippet from tries to use Object.setPrototypeOf if available, only falling back to using __proto__ in really old browsers. The reason is that it works.

Object.setPrototypeOf(B, sinon.stub().callsFake(function() { console.log('I am Super Stub!'); } ));
new B(); // prints am Super Stub!
Object.getPrototypeOf(B).callCount // 1

See this gist for more. Also this in case you wonder about ES5 inheritance.

@fatso83 Thanks for the gist. That helped me stubbing classes.
But how do I spy on classes properly? I tried different ways, but the only way I could really spy on a constructor was the following:

  it('test sinon constructor spy with container', () => {
    const testClassContainer = {};
    testClassContainer.TestClass = class TestClass {
      constructor() {
        console.log('testClass created');
      }
    }
    const a = sinon.spy(testClassContainer, 'TestClass');
    console.log('called', a.called); // false
    new testClassContainer.TestClass();
    console.log('called', a.called); // true
  });

That looks like a usage question; please post it to StackOverflow and tag it with sinon, so the bigger community can help answer your questions.

A lot more people follow that tag that will help you.

Here's what works for me:

test('constructor', async () => {
  const constructorStub = sinon.stub()

  function MyClass (...args) {
    return constructorStub(...args)
  }

  new MyClass({ some: 'args' })

  sinon.assert.calledWith(constructorStub, { some: 'args' })
})

@simoneb Are you sure that even makes sense simon? You just implemented the entire object to test as something that returns a stub. That is something that is not possible/makes sense in any production system.

This is not "stubbing the constructor", btw. This is creating a constructor that returns a function and testing if the returned function is called. The constructor is still MyClass - and that is not a stub (nor can it be).

@simoneb Are you sure that even makes sense simon? You just implemented the entire object to test as something that returns a stub. That is something that is not possible/makes sense in any production system.

This is not "stubbing the constructor", btw. This is creating a constructor that returns a function and testing if the returned function is called. The constructor is still MyClass - and that is not a stub (nor can it be).

Yes this is not stubbing the constructor but when used in conjunction with something like proxyquire, which you can use to mock module exports, you can replace the constructor of an external library with MyClass and check how the constructor is invoked. I should have been clearer.

How about replacing the entire class as a stub instead?

import sinon from 'sinon';
import * as exampleModule from 'exampleModule';

describe('example', function(){
  it(function() {
    const classStubbedInstance = Sinon.createStubInstance(exampleModule.ExampleClass);
    const constructorStub = Sinon.stub(module, 'ExampleClass').returns(classStubbedInstance);

    sinon.assert.calledWith({some: 'args'})
  })
)

So far it's worked for some simple cases for me.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

stephanwlee picture stephanwlee  ·  3Comments

byohay picture byohay  ·  3Comments

fearphage picture fearphage  ·  3Comments

OscarF picture OscarF  ·  4Comments

fearphage picture fearphage  ·  4Comments