Ruby on rails RSpec模拟调用函数参数

Ruby on rails RSpec模拟调用函数参数,ruby-on-rails,ruby,ruby-on-rails-4,rspec,mocking,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 =

如何模拟调用正在使用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
并且它会引发一个错误“#未实现:item_类” 我试图使用class_-double和instance_-double,但没有用

失败:

1) 模块1#查找项目 失败/错误:允许(虚拟类)。接收(:项类) #未实现:项\类 #./spec/…_spec.rb:26:in‘分块(3层)in’


26行:允许(虚拟类)。接收(:项目类)。和返回(项目类)

不确定为什么要这样做,但距离不太远

当前您正在尝试使用

allow(dummy_class).to receive(:item_class).and_return(item_class)
但是
item\u类
不是一个方法,它只是一个
local\u变量

简而言之,语法是
allow(object).to receive(方法名称).with(参数).and(返回值)

语法是
expect(object).to receive(方法名称).with(参数).and_return(返回值)

因此,您的意思可能是
allow(dummy_class).to receive(:find_item).with(item_class).and_return(item_class)
,因为
find_item
是实际调用的方法,
item_class
是传入的参数,但由于您正在存根返回值,因此方法体永远不会发生

此外,您还可以
allow(dummy_class.)。接收(:find_item)。并调用原始版本的
dummy_class
,但这并没有真正起作用,因为
dummy_class
不是一个双精度类,并且已经“允许”调用其原始版本的
find_item

因此,让我们使用
dummy\u class
的本机功能,只需
allow(item\u class)。要接收(:find\u或\u initialize\u 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
的部分双精度,并跳过
Item\u类

#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
很好,因为他们可以验证double,并确保
对象在存根之前实际定义了该方法

考虑到测试的性质以及
dummy_class
不是双精度(因此无需
允许任何内容)的事实,您没有测试任何返回值,我建议只使用调用,因为它们仅用于消息预期。
这使得测试更简单、更清晰,并且没有任何依赖性:

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   
它们也有部分双重味道,但这取决于
是一个已知和加载的
对象(与上述非常类似,但预期是调用的后遗症)

另外,让我们假设
模块1
中的
(…)
看起来像
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  
这确保了不仅进行了调用,而且将预期参数传递给了调用


特别是在
模块
测试消息期望时,我会尝试继续测试Double和Spies,因为它使您的测试独立且快速(当
不再存在时会发生什么?

您好,您能添加更多关于错误的信息吗?我更新了我的帖子
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