RSpec模拟调用函数参数

时间:2017-04-27 11:24:55

标签: ruby-on-rails ruby ruby-on-rails-4 rspec mocking

如何模拟调用我正在使用RSpec测试的函数参数 我是这样做的:

module Module1
  def find_item(str, item_class)
    arr = item_class.find_or_initialize_by(...)
  end
end

然后在规格中:

let!(:dummy_class) { Class.new { extend Module1 } }

it 'calls find_or_initialize_by' do
  item_class = double("Item")
  allow(dummy_class).to receive(:item_class).and_return(item_class)
  expect(item_class).to receive(:find_or_initialize_by)
  dummy_class.find_item("item1", Item)
end

它引发错误“#does not implement:item_class” 我试图使用class_double和instance_double,但它没有用。

  

故障:

     

1)Module1#find_items      失败/错误:允许(dummy_class).to receive(:item_class)       #没有实现:item_class    #./spec/..._spec.rb:26:在'

中的块(3级)

26行:allow(dummy_class).to receive(:item_class).and_return(item_class)

1 个答案:

答案 0 :(得分:1)

不知道你为什么要这样做,但你并不太远。

目前您正在尝试使用

allow(dummy_class).to receive(:item_class).and_return(item_class)

item_class不是一种local_variable的方法。

Message Allowance简而言之,语法为allow(object).to receive(method_name).with(arguments).and_return(return_value)

Message Expectation语法为expect(object).to receive(method_name).with(arguments).and_return(return_value)

所以也许你的意思是allow(dummy_class).to receive(:find_item).with(item_class).and_return(item_class),因为find_item是实际被调用的方法而item_class是传入的参数,但是因为你正在查看返回值然后是方法永远不会发生

您还可以allow(dummy_class).to recieve(:find_item).and_call_original,但这并不是真正有用的,因为dummy_class不是双倍的,并且已经“允许”调用其find_item的原始版本。

因此,让我们继续使用dummy_classallow(item_class).to receive(:find_or_initialize_by)的原生功能,然后以下内容将起作用。

it 'calls find_or_initialize_by' do
  item_class = double("Item")
  allow(item_class).to recieve(:find_or_initialize_by) #needed because it is a test double and knows nothing
  expect(item_class).to receive(:find_or_initialize_by) 
  dummy_class.find_item("item1", item_class) #used test double here to trap messages
end

或者,我们可以使用Item的部分双倍,并跳过Test Double item_class,例如。

#please note this binds the test to Item existing
it 'calls find_or_initialize_by' do
  allow(Item).to receive(:find_or_initialize_by) #now a stubbed method on a partial double(Item)
  expect(Item).to receive(:find_or_initialize_by) 
  dummy_class.find_item("item1", Item) #used the partial double
end

Partial doubles很好,因为它们可以验证双打,并确保Object在存根之前实际定义该方法。

考虑到你的考试的性质以及dummy_class不是一个双重(因此不需要allow任何东西)并且你没有测试任何返回值而只是调用我会这么说建议只使用Spy,因为它们仅用于预期消息。 这使得测试更简单,更清晰,没有任何依赖性:

it 'calls find_or_initialize_by' do
  item = spy("item")
  dummy_class.find_item("item1", item)
  expect(item).to have_received(:find_or_initialize_by)
end   

它们也有部分双重味道,但这取决于Item是已知并加载Object :(非常类似于上述但预期是呼叫的后效)

#please note this binds the test to Item existing
it 'calls find_or_initialize_by' do
  allow(Item).to receive(:find_or_initialize_by)
  dummy_class.find_item("item1", Item)
  expect(Item).to have_received(:find_or_initialize_by)
end 

另外,假设(...)中的Module1看起来像name: str,那么我建议测试它是否也使用正确的参数进行调用,例如

it 'calls find_or_initialize_by with args' do
  item = spy("item")
  dummy_class.find_item("item1", item)
  expect(item).to have_received(:find_or_initialize_by).with(name: 'item1')
end  

这样可以确保不仅进行了调用,而且还将预期的参数传递给了调用。

特别是对Module测试消息期望我会尝试保持测试双打和间谍,因为它会使您的测试独立且快速(当Item不再存在时会发生什么?)