Ruby 如何在RSpec中使用验证双精度设置负面消息预期?
我想在以下代码中测试对Ruby 如何在RSpec中使用验证双精度设置负面消息预期?,ruby,rspec,Ruby,Rspec,我想在以下代码中测试对obj.my_method的条件调用: def method_to_test(obj) # ... obj.my_method if obj.respond_to?(:my_method) # ... end obj可以是Foo或Bar。一个实现了my_方法,另一个没有: class Foo def my_method; end end class Bar end 我的测试目前的结构如下: describe '#method_to_test' do
obj.my_method
的条件调用:
def method_to_test(obj)
# ...
obj.my_method if obj.respond_to?(:my_method)
# ...
end
obj
可以是Foo
或Bar
。一个实现了my_方法
,另一个没有:
class Foo
def my_method; end
end
class Bar
end
我的测试目前的结构如下:
describe '#method_to_test' do
context 'when obj is a Foo' do
let(:obj) { instance_double('Foo') }
it 'calls my_method' do
expect(obj).to receive(:my_method)
method_to_test(obj)
end
end
context 'when obj is a Bar' do
let(:obj) { instance_double('Bar') }
it 'does not call my_method' do
expect(obj).not_to receive(:my_method) # <- raises an error
method_to_test(obj)
end
end
end
def my_method
raise # should never be executed in any case
end
def respond_to?(m_name)
m_name != :my_method && super(name)
end
我完全理解RSpec提出这个错误的原因。尽管obj
是一个双重验证码,我如何测试该行
PS:我已经启用了,所以将
实例双('Bar')
更改为对象双('Bar.new)
或只是Bar.new
没有帮助。有趣而棘手的问题!从Rspec文档中,我发现:
[编辑:刚刚看到您已经理解了rspec投诉的原因,因此您可以跳转到可能的脏/不脏的解决方案部分。我将保留我在此处找到的解释,以防其他人遇到类似问题(请纠正我/添加更多相关信息!)]
验证双重实例不支持类报告为不存在的方法
因为需要根据类的实际实例进行验证。这是常见的
使用方法_缺失时的情况。
-
这些文档引用了instance_double的问题和method_missing的用法,但我相信这与您面临的问题是一样的。您的Bar类没有报告以响应:my_method。事实上,在失败的测试中,method\u to\u test(obj)
从未被调用,因为当您尝试在实例上定义期望值时,测试失败:
describe '#method_to_test' do
context 'when obj is a Bar' do
let(:obj) { instance_double('Bar') }
it 'does not call my_method' do
puts "A"
expect(obj).not_to receive(:my_method) # <- raises an error
puts "B" # <- never runs
method_to_test(obj)
end
end
end
当您尝试添加期望值时,Rspec会查找Bar类报告已定义的方法的列表,但没有找到:my_方法
,并以预防性方式失败,认为它实际上是在帮助您识别测试中的问题(毕竟,您试图在不存在的东西上添加一个方法调用期望,所以这很可能是一个输入错误,对吧?——哎哟!)
可能的脏溶液
因此,恐怕没有一种一致的方法可以将此期望添加到您正在使用的实例中,而不必进行肮脏的欺骗。例如,您可以实际定义:我的方法在条形图上,这样您就可以添加消息期望,但是您必须在上下文中重新定义响应?
对吧,结果是这样的:
describe '#method_to_test' do
context 'when obj is a Foo' do
let(:obj) { instance_double('Foo') }
it 'calls my_method' do
expect(obj).to receive(:my_method)
method_to_test(obj)
end
end
context 'when obj is a Bar' do
let(:obj) { instance_double('Bar') }
it 'does not call my_method' do
expect(obj).not_to receive(:my_method) # <- raises an error
method_to_test(obj)
end
end
end
def my_method
raise # should never be executed in any case
end
def respond_to?(m_name)
m_name != :my_method && super(name)
end
但是你为什么要这样做呢?这既肮脏又违反直觉!这种策略可以在我们正在讨论的非常具体的情况下起作用,但是这会将你的测试与你的具体实现结合起来,并且使它们作为文档的价值无效
可能的低污染解决方案
因此,我的建议是不要依赖于方法期望,而是依赖于所需的行为。您想要的是执行method\u to\u test(obj)
不会因为Bar实例上缺少方法而引发异常。您可以通过以下方式完成:
describe '#method_to_test' do
context 'when obj is a Bar' do
let(:obj) { instance_double('Bar') }
it 'does not call my_method' do
expect { method_to_test(obj) }.not_to raise_error
end
end
end
缺点是,如果在执行方法体时引发任何异常,则此测试将失败,而理想情况下,我们希望只有在引发NoMethodError
时它才会失败
it 'does not call my_method' do
expect { method_to_test(obj) }.not_to raise_error(NoMethodError)
end
原因是,在obj上调用my_方法时引发的错误将不是类型为NoMethodError
,而是类型为RSpec::Mocks::MockExpectationError
:
#method_to_test
when obj is a Bar
does not call my_method (FAILED - 1)
Failures:
1) #method_to_test when obj is a Bar does not call my_method
Failure/Error: expect { method_to_test(obj) }.not_to raise_error
expected no Exception, got #<RSpec::Mocks::MockExpectationError: #<InstanceDouble(Bar) (anonymous)> received unexpected message :my_method with (no args)> with backtrace:
#方法对测试
当obj是一个酒吧
不调用my_方法(失败-1)
失败:
1) #方法_to _测试当obj为条形时,不调用我的方法
失败/错误:预期{method_to_test(obj)}。not_引发错误
不应出现异常,使用回溯获取#:
您可以使用RSpec::Mocks::MockExpectationError
而不是NoMethodError
来实现预期,但这也会创建脆弱的测试:如果您将测试条实例更改为一个真实的obj而不是一个双实例,那么如果调用了my_方法,会引发NoMethodError
?这项测试将继续通过,而不测试任何有价值的东西。此外(如评论中指出的),如果您这样做,RSPEC将警告您:
警告:使用期望{}。NoTyto to RaSeSeError(TraceReloReC类)会产生假阳性,因为字面上任何其他错误都会导致预期通过,包括露比提出的那些(例如NoththoDror,NameError和AgulSturror),这意味着您要测试的代码可能甚至没有达到。.not_引发_错误。可以通过设置:RSpec::expections.configuration.warn_about_potential_false_positives=false来抑制此消息。“
因此,在这些选项之间,我将不指定异常的类型
可能的(最佳)解决方案
最后,我建议重构。使用respond\u to?
通常表示您缺少一个适当的接口来跨这些多个类定义和实现。我无法帮助您了解重构的细节,因为这在很大程度上取决于您当前的代码上下文/实现细节。然而,您应该从成本角度评估它是否值得,如果不值得,我建议将obj.my\u method if obj.respond\u to?(:my\u method)
提取到一个专用的方法调用中,这样断言它不会引发错误将是一个更可靠的断言,因为您可以在较少的上下文中引发奇怪的异常。此外,如果您这样做,您可以传递一个简单的double
对象,该对象允许您按照最初的方式添加消息期望
很抱歉——冗长的回答:D谢谢你详细的回答。关于“可能的肮脏解决方案”,这将是非常具体的实现。如果obj.is_a?(Fo