您何时会在Rspec中使用双打间谍?

时间:2018-10-08 21:49:41

标签: ruby rspec

我可以理解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查询。因此,我们使用一个测试double并将其存根,以解决需求。

但是后来我遇到了间谍。而且我认为没有用例。这是教程中提供的示例:

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 :(得分:1)

间谍会跟踪对其发出的呼叫(特别是发送的消息)。因此,只要需要断言在协作者上进行了特定的呼叫,就可以使用间谍。

典型的用例是根据输入来检查您的实现是否正在使用外部协作者。假设您打算有条件地登录,或者您可能要检查作业是否已包含特定的参数,或者某个邮件程序方法被称为...

间谍是确保对象正确协作的工具。

更新

可以在@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

答案 1 :(得分:0)

我会在rewritten wrote

的基础上再加上一个案例

间谍为您提供了一定的灵活性。如果您需要检查该方法是否已被调用,则可以在之前的模拟中完成它:

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

specify do
  bar.run
end

但是before并不是增加期望值的最佳场所。您是supposed to separate setup, run and test stages。您可以这样做:

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_conditionsexpect(foo).not_to have_received(:do_stuff))。无论与上下文“上方”没有什么不同,都不应出现在更具体的上下文“下方”中。它有助于管理更大的规格。

*注意:我不确定以这种方式定义的before块的顺序,在查看rspec docs之后,我不确定该顺序是否得到保证。那一刻无法检查。但是,仅出于演示目的,我们可以假设before { set_up_special_conditions }将在before { bar.run }之前运行。但事实并非如此-还有其他方法可以确保这一点,但这似乎超出了此问题的范围。