Can ES6 constructors be stubbed more easily with Sinon?

丶灬走出姿态 提交于 2019-12-11 18:37:15

问题


Given the (overly simplified) snippet:

import Validator from 'validator';

export default function isValid(arg) {
  // Validator#isValid is an ES6 getter
  return new Validator(arg).isValid;
}   

How can I test that a Validator was instantiated with the given parameter? And stub isValid?


I know I can restructure my code to avoid the issue, I am not looking for a workaround as I found plenty (dependency injection, not using ES6 sugar, etc.).

I found a way, but it is terribly ugly. In test file:

import ValidatorNamespace from 'validator';

const Validator = ValidatorNamespace.default;
let validatorInstance;
let validatorConstructor;

const subject = arg => isValid(arg);
const validityStatus = true;

describe('isValid helper', () => {
  beforeEach(() => {
    validatorInstance = sinon.createStubInstance(Validator);

    // Yay! This is how I managed to spy on the constructor!.. :(
    validatorConstructor = sandbox.stub(ValidatorNamespace, 'default').
      returns(validatorInstance);

    sandbox.stub(validatorInstance, 'isValid').value(validityStatus);
  });

  it('instantiates the validator properly', ()=> {
    subject('arg');
    expect(validatorConstructor).to.have.been.calledWith('arg')
  });

  it('returns the value returned by the validator', ()=> {
    expect(subject('arg')).to.eq(validityStatus);
  });
});

Validator code:

export default class Validator {
  constructor(arg) {
    this.innerValue = arg;
  }

  get isValid() {
    return aFunctionOf(this.innerValue);
  }
}

回答1:


What you want isn't really possible. Stubbing requires some kind of "seam" through which to put the stubs in place. When you import functions (constructors or otherwise) directly in your production code, the only seam you're leaving is the import process itself.

There is proxyquire, which overrides require calls in node. I don't know what environment you're using, and I don't really know how well this plays with ES6 modules. If you're transpiling to ES6 using babel, though, it should work.

In my experience this kind of stuff is not worth the additional complexity. My usual workaround is to just make a static factory function and stub/use that instead of using the constructor directly:

export default class Validator {
  constructor(arg) {
    this.innerValue = arg;
  }

  static create(arg) {
    return new Validator(arg);
  }

  get isValid() {
    return aFunctionOf(this.innerValue);
  }
}

If you want a unit test for the factory, you can simply check the returned instance instead of stubbing the constructor:

it('create should return an instance', function() {
  let arg = { foo: 'bar' };

  let result = Validator.create(arg);

  expect(result).to.be.an.instanceof(Validator);
  expect(result.innerValue).to.equal(arg);
});


来源:https://stackoverflow.com/questions/49142026/can-es6-constructors-be-stubbed-more-easily-with-sinon

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!