简体   繁体   中英

How to stub ES6 class constructors with Sinon

I'm trying stub class constructors with Sinon.

The stub of the regular method 'omg' works fine, but the stub of the constructor fails the test, and the 'real' constructor is called instead of the stub.

Any ideas on what syntax I need to stub this correctly?

 class Foo { constructor() { this.bar = new Bar(); } omg() { this.bar.omg(); } } class Bar { constructor() { console.log('In bar constructor'); } omg() { console.log('In bar omg'); } } const sandbox = sinon.createSandbox(); sandbox.stub(Bar.prototype, 'constructor'); sandbox.stub(Bar.prototype, 'omg'); describe('Foo', () => { describe('Constructor', () => { it('Should instantiate bar', () => { const foo = new Foo(); expect(Bar.prototype.constructor.called).to.be.true; }); }); describe('Omg', () => { it("Should call Bar's omg method', () => { const foo = new Foo(); foo.omg(); expect(Bar.prototype.omg.called).to.be.true; }); }); });

Its been more than 2 years, means this is hard. :)

When it is hard to do, then the code need refactoring.

Below example works but may not usable at real world (need to define Bar as stub before define Foo).

Highlight:

  • usage of stub calledWithNew() to check whether Bar called with new.
  • not use arrow functions .
  • Usage of chai's expect instanceof , because of Foo constructor will add property bar , then, additional expectation can be added to check property bar, which means check whether Foo's construction function run.
// @file stackoverflow.js
const sinon = require('sinon');
const { expect } = require('chai');

const sandbox = sinon.createSandbox();

// Define Bar as a stub.
const Bar = sandbox.stub();
const BarOmgFn = sinon.fake();
Bar.prototype.omg = BarOmgFn;

// class Bar {
//   constructor() {
//     console.log('In bar constructor');
//   }
//   omg() {
//     console.log('In bar omg');
//   }
// }

class Foo {
  constructor() {
    this.bar = new Bar();
  }

  omg() {
    this.bar.omg();
  }
}

describe('Foo', function () {
  after(function () {
    sandbox.restore();
  });
  describe('Constructor', function () {
    it('Should instantiate bar', function () {
      const foo = new Foo();

      // Check whether Bar called with new.
      expect(Bar.calledWithNew()).to.equal(true);
      // Check bar property.
      expect(foo.bar).to.be.instanceOf(Bar);
    });
  });

  describe('Omg', () => {
    it("Should call Bar's omg method", function () {
      const foo = new Foo();
      foo.omg();

      expect(BarOmgFn.calledOnce).to.equal(true);
    });
  });
});

Run the example using mocha:

$ npx mocha stackoverflow.js


  Foo
    Constructor
      ✓ Should instantiate bar
    Omg
      ✓ Should call Bar's omg method


  2 passing (14ms)

$

From design pattern point of view, class Foo is highly dependent to class Bar. Refactoring can use dependency injection pattern . For example: simple change to class Foo, by require 1 argument, which is Bar itself or instance of Bar. This change make the code easier to test.

class Foo {
  // Now constructor need 1 argument
  constructor(Bar) {
    this.bar = new Bar();
  }

  omg() {
    this.bar.omg();
  }
}

Hope this helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM