Когда бы вы использовали шпионов над двойниками в Rspec? - PullRequest
0 голосов
/ 09 октября 2018

Я могу понять применение тестовых двойников в Rspec.Возможно, вы захотите протестировать метод, у которого есть объект, который выполняет дорогой сетевой запрос.Таким образом, вы используете заполнение (двойной тест) для возврата значения, которое вам нужно из дорогой операции:

class Contact
  def update
    @api_result = AmazonAPI.perform_expensive_task
    self.status = @api_result['status']
    self.last_checked = @api_result['last_checked']
    save!
  end
end

describe Contact do
  subject { Contact.new }

  describe '#update' do
    it "updates contact with api criteria" do 
      api = double('Amazon API')
      allow(api).to receive(:perform_expensive_task).and_return({ status: 1, last_checked: Time.now })
      subject.update
      expect(subject.status).to eq 1
    end
  end
end

Нам нужно протестировать метод обновления, но мы не хотим тестировать APIзапрос.Поэтому мы используем двойной тест и заглушаем его, чтобы обойти требование.

Но потом я сталкиваюсь со шпионами.И я не вижу смысла в этом.Это пример, приведенный в руководстве:

let(:order) do
  spy('Order', process_line_items: nil, charge_credit_card: true, send_email: true)
end

before(:example) do
  order.process_line_items
  order.charge_credit_card
  order.send_email
end

it 'calls #process_line_items on the order' do
  expect(order).to have_received(:process_line_items)
end

it 'calls #charge_credit_card on the order' do
  expect(order).to have_received(:charge_credit_card)
end

it 'calls #send_email on the order' do
  expect(order).to have_received(:send_email)
end

Этот конкретный пример явно вызывает три метода, которые он позже проверяет, вызывал ли он.Конечно, это называется им.Он сделал это прямо в тесте.В реальных ситуациях, когда я должен использовать шпионов?

Ответы [ 2 ]

0 голосов
/ 09 октября 2018

Я бы добавил еще один случай к тому, что переписал написал

Шпионы дают вам некоторую гибкость.Если вам нужно проверить, что метод был вызван, вы можете сделать это с помощью mocks в before:

before do
  expect(foo).to receive(:do_stuff)
end

specify do
  bar.run
end

Но before - не лучшее место для добавления ожиданий.Вы должны разделить этапы настройки, запуска и тестирования .Вы можете сделать это:

specify do 
  expect(foo).to receive(:do_stuff)
  bar.run
end

Но это выглядит лучше, если вы сделаете

before do
  bar.run
end

specify do
  expect(foo).to have_received(:do_stuff)
end

У вас будет более понятный код, когда есть еще что проверить

before { bar.run }

specify do
  expect(foo).to have_received(:do_stuff)
end

context 'with some special conditions' do
  before { set_up_special_conditions }

  specify do
    expect(foo).not_to have_received(:do_stuff)
  end
end

Возможно, это не имеет большого значения, и вы все равно можете жить с

specify do
  bar.run
  expect(foo).to have_received(:do_stuff)
end

context 'with some special conditions' do
  before { set_up_special_conditions } # * check the note at the bottom

  specify do
    bar.run
    expect(foo).not_to have_received(:do_stuff)
  end
end

Но я думаю, что хороший подход к определению контекстов состоит в том, чтобы явно иметь только существенные различия (set_up_special_conditions и expect(foo).not_to have_received(:do_stuff) в примере)упоминается.Все, что не отличается от контекста «выше», не должно появляться в более конкретном контексте «ниже».Это помогает управлять большими спецификациями.

* Примечание: я не уверен насчет порядка блоков before, определенных таким образом, и после просмотра rspec docs я неуверен, что этот заказ гарантирован.Не могу проверить прямо сейчас.Но только для демонстрационных целей мы можем делать вид, что before { set_up_special_conditions } будет работать до before { bar.run }.Но если это не так - есть другие способы обеспечить это, но это выходит за рамки этого вопроса.

0 голосов
/ 09 октября 2018

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

Типичным примером использования является проверка того, что ваша реализация использует / не использует внешнего коллаборатора в зависимости от ввода.Скажем, вы намереваетесь войти в систему условно, или вы можете проверить, что задание поставлено в очередь с конкретными аргументами, или вызван определенный почтовый метод ...

Шпионы - это инструмент, гарантирующий правильное взаимодействие ваших объектов.

ОБНОВЛЕНИЕ

Примеры можно найти в ответе @ meta, https://stackoverflow.com/a/52717158/384417.

Простой вариант использования с кодом - это логгер:

class SomeCommand
  def call(arg:, other:)
    if arg <= 0
      logger.warn("args should be positive")
    else
      logger.debug("all fine")
    end
    # more
  end

  def logger
    Rails.logger # for instance
  end
end

describe SomeCommand
  let(:logger) { spy('Logger') }

  # replace collaborator
  before { allow(subject).to receive(:logger) { logger } }

  context 'with negative value' do
    it 'warns' do
      subject.call(arg: -1, other: 6)
      expect(logger).to have_received(:warn)
      expect(logger).not_to have_received(:debug)
    end
  end

  context 'with positive value' do
    it 'logs as debug' do
      subject.call(arg: 1, other: 6)
      expect(logger).not_to have_received(:warn)
      expect(logger).to have_received(:debug)
    end
  end
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...