ES2016 Class, Sinon Stub Constructor

20,803

Solution 1

You'll need to setPrototypeOf the subClass due to the way JavaScript implements inheritance.

const sinon = require("sinon");

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

class Bar extends Foo {
  constructor() {
    super('test');
  }
}

describe('Example', () => {
  it('should stub super.constructor call', () => {
    const stub = sinon.stub().callsFake();
    Object.setPrototypeOf(Bar, stub);

    new Bar();

    sinon.assert.calledOnce(stub);
  });
});

Solution 2

You need to spy instead of stub,

sinon.spy(Foo.prototype, 'constructor');

describe('Example', () => {
  it('should stub super.constructor call', () => {
    const costructorSpy = sinon.spy(Foo.prototype, 'constructor');
    new Bar();
    expect(costructorSpy.callCount).to.equal(1);
  });
});

*****Update****** Above was not working as expected, I added this way and is working now.

 describe('Example', () => {
    it('should stub super.constructor call', () => {
      const FooStub = spy(() => sinon.createStubInstance(Foo));
      expect(FooStub).to.have.been.calledWithNew;
    });
 });

Solution 3

Adding to the accepted answer of Wenshan, there is one step that may be overlooked when stubbing the parent class and replacing the original parent class with setPrototypeOf.

💡 Additionally, to avoid it breaking the succeeding tests it is a good idea to set back the original parent class at the end, like:

const sinon = require("sinon");
    
class Foo {
  constructor(message) {
    console.log(message);
   }
}
    
class Bar extends Foo {
  constructor() {
    super('test');
  }
}
    
describe('Bar constructor', () => {
  it('should call super', () => {
    const stub = sinon.stub().callsFake();
    const original = Object.getPrototypeOf(Bar);  // Bar.__proto__ === Foo
    

    Object.setPrototypeOf(Bar, stub);             // Bar.__proto__ === stub
    
    new Bar();
    
    sinon.assert.calledOnce(stub);
    Object.setPrototypeOf(Bar, original);         // Bar.__proto__ === Foo

  });
});

The addition is

// saving the reference to the original parent class:
const original = Object.getPrototypeOf(Bar);
// setting back the original parent class after stubbing and the assertion:
Object.setPrototypeOf(Bar, original); 
Share:
20,803
klyd
Author by

klyd

Updated on May 10, 2021

Comments

  • klyd
    klyd almost 3 years

    I'm trying to stub out a super call with sinon, and es2016 but I'm not having much luck. Any ideas why this isn't working?

    Running Node 6.2.2, this might be an issue with its implementation of classes/constructors.

    .babelrc file:

    {
      "presets": [
        "es2016"
      ],
      "plugins": [
        "transform-es2015-modules-commonjs",
        "transform-async-to-generator"
      ]
    }
    

    Test:

    import sinon from 'sinon';
    
    class Foo {
      constructor(message) {
        console.log(message)
      }
    }
    
    class Bar extends Foo {
      constructor() {
        super('test');
      }
    }
    
    describe('Example', () => {
      it('should stub super.constructor call', () => {
        sinon.stub(Foo.prototype, 'constructor');
    
        new Bar();
    
        sinon.assert.calledOnce(Foo.prototype.constructor);
      });
    });
    

    Result:

    test
    AssertError: expected constructor to be called once but was called 0 times
        at Object.fail (node_modules\sinon\lib\sinon\assert.js:110:29)
        at failAssertion (node_modules\sinon\lib\sinon\assert.js:69:24)
        at Object.assert.(anonymous function) [as calledOnce] (node_modules\sinon\lib\sinon\assert.js:94:21)
        at Context.it (/test/classtest.spec.js:21:18)
    

    Note: this issue seems to only happen for constructors. I can spy on methods inherited from the parent class without any issues.