Можно ли упростить конструкторы ES6 с помощью Sinon?

Учитывая (чрезмерно упрощенный) фрагмент:

import Validator from 'validator';

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

Как я могу проверить, что Validator был создан с заданным параметром? А заглушка isValid?


Я знаю, что могу реструктурировать свой код, чтобы избежать этой проблемы, я не ищу обходного пути, поскольку я нашел много (внедрение зависимостей, не использование сахара ES6 и т. д.).

Я нашел способ, но он ужасно некрасивый. В тестовом файле:

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);
  });
});

Код валидатора:

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

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

person qnilab    schedule 07.03.2018    source источник


Ответы (1)


То, что вы хотите, на самом деле невозможно. Заглушки требуют некоторого «шва», через который заглушки помещаются на место. Когда вы импортируете функции (конструкторы или что-то еще) непосредственно в ваш производственный код, единственный шов, который вы оставляете, — это сам процесс импорта.

Существует proxyquire, который переопределяет require вызовы в node. Я не знаю, какую среду вы используете, и я действительно не знаю, насколько хорошо это работает с модулями ES6. Однако, если вы выполняете транспиляцию в ES6 с помощью babel, это должно сработать.

По моему опыту, такие вещи не стоят дополнительной сложности. Мой обычный обходной путь - просто создать статическую фабричную функцию и заглушить/использовать ее вместо прямого использования конструктора:

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

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

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

Если вам нужен модульный тест для фабрики, вы можете просто проверить возвращаемый экземпляр вместо заглушки конструктора:

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);
});
person sripberger    schedule 29.03.2018