Подтвердить что ты не робот

Завершение всего класса для тестирования в силоне

Преамбула: я читал много сообщений SO и в блогах, но ничего не видел, чтобы ответить на этот конкретный вопрос. Может быть, я просто искал неправильную вещь...

Предположим, что я разрабатываю класс WidgetManager, который будет работать с объектами Widget.

Как использовать sinon для проверки того, что WidgetManager правильно использует API Widget, не втягивая всю библиотеку Widget?

Обоснование: тесты для WidgetManager должны быть отделены от класса Widget. Возможно, я еще не написал Widget, или, возможно, Widget - это внешняя библиотека. В любом случае, я должен проверить, что WidgetManager правильно использует Widget API без создания реальных виджетов.

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

Чтобы сделать его конкретным, как бы я мог проверить, что Widget.create() получает точно один раз с единственным аргументом "имя" в следующем коде?

проверенный код

// file: widget-manager.js

function WidgetManager() {
   this.widgets = []
}

WidgetManager.prototype.addWidget = function(name) {
    this.widgets.push(Widget.create(name));
}

код тестирования

// file: widget-manager-test.js

var WidgetManager = require('../lib/widget-manager.js')
var sinon = require('sinon');

describe('WidgetManager', function() {
  describe('#addWidget', function() {
    it('should call Widget.create with the correct name', function() {
      var widget_manager = new WidgetManager();
      // what goes here?
    });

    it('should push one widget onto the widgets list', function() {
      var widget_manager = new WidgetManager();
      // what setup goes here?
      widget_manager.addWidget('fred');
      expect(widget_manager.widgets.length).to.equal(1);
  });
});

Помимо этого: Конечно, я мог бы определить класс MockWidget для тестирования с помощью соответствующих методов, но мне больше интересно узнать, как правильно использовать функции sinon spy/stub/mock.

4b9b3361

Ответ 1

Ответ на вопрос об инъекции зависимостей.

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

В зависимости от того, как создается WidgetManager, существует несколько вариантов инъекции зависимостей.

Простым методом является включение в конструкцию WidgetManager зависимостей Widget:

// file: widget-manager.js

function WidgetManager(Widget) {
   this.Widget = Widget;
   this.widgets = [];
}

WidgetManager.prototype.addWidget = function(name) {
    this.widgets.push(this.Widget.create(name));
}

И затем в своем тесте вы просто проходите пронумерованный Widget к тесту WidgetManager:

it('should call Widget.create with the correct name', function() {
  var stubbedWidget = {
      create: sinon.stub()
  }
  var widget_manager = new WidgetManager(stubbedWidget);
  widget_manager.addWidget('fred');
  expect(stubbedWidget.create.calledOnce);
  expect(stubbedWidget.create.args[0] === 'fred');
});

Вы можете изменить поведение своего заглушки в зависимости от потребностей конкретного теста. Например, чтобы проверить, что длина списка виджетов увеличивается после создания виджета, вы можете просто вернуть объект из вашего обрезанного метода create():

  var stubbedWidget = {
      create: sinon.stub().returns({})
  }

Это позволяет вам полностью контролировать зависимость, без необходимости издеваться или заглушать все методы, а также проверять взаимодействие с его API.

Есть также такие опции, как proxyquire или rewire, которые предоставляют более мощные опции для переопределения зависимостей во время тестирования. Наиболее подходящим вариантом является реализация и предпочтение - но во всех случаях вы просто пытаетесь заменить данную зависимость во время тестирования.

Ответ 2

Ваш метод addWidget выполняет 2 действия:

  • "преобразует" строку в экземпляр Widget;
  • добавляет этот экземпляр во внутреннее хранилище.

Я предлагаю вам изменить подпись addWidget, чтобы принять экземпляр напрямую, вместо имени, и переместить создание другого места. Облегчит тестирование:

Manager.prototype.addWidget = function (widget) {
  this.widgets.push(widget);
}

// no stubs needed for testing:
const manager = new Manager();
const widget = {};

manager.addWidget(widget);
assert.deepStrictEquals(manager.widgets, [widget]);

После этого вам понадобится способ создания виджетов по имени, что также должно быть довольно простым для тестирования:

// Maybe this belongs to other place, not necessarily Manager class…
Manager.createWidget = function (name) {
  return new Widget(name);
}

assert(Manager.createWidget('calendar') instanceof Widget);