6

I would like to mock out super calls, especially constructors in some ES6 classes. For example

import Bar from 'bar';

class Foo extends Bar {
  constructor(opts) {
    ...
    super(opts);
  }

  someFunc() {
    super.someFunc('asdf');
  }
}

And then in my test, I would like to do something like

import Foo from '../lib/foo';
import Bar from 'bar';

describe('constructor', function() {
  it('should call super', function() {
    let opts = Symbol('opts');
    let constructorStub = sinon.stub(Bar, 'constructor');
    new Foo(opts);
    sinon.assert.calledWith(constructorStub, opts);
  });
})

describe('someFunc', function() {
  it('should call super', function() {
    let funcStub = sinon.stub(Bar, 'someFunc');
    let foo = new Foo(opts);
    foo.someFunc();
    sinon.assert.calledWith(funcStub, 'asdf');
  });
})    
aviit
  • 1,957
  • 1
  • 27
  • 50
Fotios
  • 3,643
  • 1
  • 28
  • 30
  • 2
    When the `super` keyword is used as a function call, it calls the base class constructor function. Your `someFunc` sample should probably be `super.someFunc('asdf')` – Amit Aug 26 '15 at 16:04
  • Wouldn't it need to be `sinon.stub(Bar.prototype, 'someFunc');`? – Bergi Aug 26 '15 at 16:45
  • 1
    No, you cannot stub a constructor that has already been inherited from. You'd need to inject your supervised version of `Bar` in `foo.js`. – Bergi Aug 26 '15 at 16:46
  • 1
    Why are you testing implementation? Why not just test input/output? – Mulan Aug 26 '15 at 17:17
  • @Amit: You're right, that was a typo in my example. – Fotios Aug 26 '15 at 17:25
  • @naomik - http://stackoverflow.com/questions/105007/should-i-test-private-methods-or-only-public-ones. I'd say it's inconclusive and legitimate both ways. – Amit Aug 26 '15 at 17:49

1 Answers1

9

Figured it out, and @Bergi was on the right track. In reponse to @naomik's question - My main purpose for wanting to stub this out was two fold. First, I didn't want to actually instantiate the super class, merely validate that I was calling the proper thing. The other reason (which didn't really come through since I was trying to simplify the example), was that what I really cared about was that I was doing certain things to opts that I wanted to make sure were carried through properly to the super constructor (for example, setting default values).

To make this work, I needed to dosinon.stub(Bar.prototype, 'constructor');

This is a better example and working test.

// bar.js
import Memcached from 'memcached'

export default class Bar extends Memcached {
  constructor(opts) {
    super(opts);
  }
}

// foo.js
import Bar from './bar.js';
export default class Foo extends Bar {
  constructor(opts) {
    super(opts);
  }
}

// test.js
import Foo from './foo.js';
import Bar from './bar.js';
import Memcached from 'memcached';
import sinon from 'sinon';

let sandbox;
let memcachedStub;
const opts = '127.0.0.1:11211';

describe('constructors', function() {
  beforeEach(function() {
    sandbox = sinon.sandbox.create();
    memcachedStub = sandbox.stub(Memcached.prototype, 'constructor');
  });

  afterEach(function() {
    sandbox.restore();
  });

  describe('#bar', function() {
    it('should call super', function() {
      new Bar(opts);

      sinon.assert.calledOnce(memcachedStub);
      sinon.assert.calledWithExactly(memcachedStub, opts);
    });
  });

  describe('#foo', function() {
    it('should call super', function() {
      new Foo(opts);

      sinon.assert.calledOnce(memcachedStub);
      sinon.assert.calledWithExactly(memcachedStub, opts);
    });
  });
});
Fotios
  • 3,643
  • 1
  • 28
  • 30
  • Just to note, this works because Babel implements ES6 `super` calls via `Class.prototype.constructor` lookups, but native ES6 classes look up the parent constructor via `Object.getPrototypeOf(Class)`, which isn't polyfillable on older browsers. You'll need `sandbox.stub(Memcached, '__proto__')` then I guess? – loganfsmyth Aug 27 '15 at 02:18