class ExternalObject
attr_accessor :external_object_attribute
def update_external_attribute(options = {})
self.external_object_attribute = [1,nil].sample
end
end
class A
attr_reader :my_attr, :external_obj
def initialize(external_obj)
@external_obj = external_obj
end
def main_method(options = {})
case options[:key]
when :my_key
self.my_private_method(:my_key) do
external_obj.update_external_attribute(reevaluate: true)
end
else
nil
end
end
private
def my_private_method(key)
old_value = key
external_object.external_object_attribute = nil
yield
external_object.external_object_attribute = old_value if external_object.external_object_attribute.nil?
end
end
我想在main_method
时测试options[:key] == :my_key
的以下内容:
[my_private_method
用参数:my_key
调用一次,它具有一个块{external_obj.update_external_attribute(reevaluate: true) }
,该块用参数update_external_attribute
调用external_obj
上的reevaluate: true
一次。
我能够使用my_private_method
参数测试一次:my_key
调用。
expect(subject).to receive(:my_private_method).with(:my_key).once
但是我如何测试期望的其余部分?
谢谢
如果您也发布测试,可能会更容易回答您的问题。设置,执行和资产/期望。
您可以在this older question中找到简短的答案。
您会发现对阅读yield
matchers很有用。
如果您还没有的话,我建议您嘲笑yield
。但是,除非您发布实际的测试代码,否则我无法告诉您。
我将回答您的问题。但是,接下来我将解释为什么您不应该那样做,并向您展示一种更好的方法。
在测试设置中,需要让double产生,这样代码才能进入您的代码块。
ExternalObject
行得通。测试通过。 RSpec是一个功能强大的工具。而且,它将使您摆脱困境。但是,这并不意味着您应该这样做。测试私有方法总是一个坏主意。
测试应仅测试类的公共接口。否则,您将陷入当前的实现中,从而在重构类的内部工作时导致测试失败-即使您没有更改对象的外部可见行为。
这是一种更好的方法:
RSpec.describe A do
subject(:a) { described_class.new(external_obj) }
let(:external_obj) { instance_double(ExternalObject) }
describe '#main_method' do
subject(:main_method) { a.main_method(options) }
let(:options) { { key: :my_key } }
before do
allow(a).to receive(:my_private_method).and_yield
allow(external_obj).to receive(:update_external_attribute)
main_method
end
it 'does something useful' do
expect(a)
.to have_received(:my_private_method)
.with(:my_key)
.once
expect(external_obj)
.to have_received(:update_external_attribute)
.with(reevaluate: true)
.once
end
end
end
注意,关于私有方法的期望已经消失了。现在,测试仅依赖于RSpec.describe A do
subject(:a) { described_class.new(external_obj) }
let(:external_obj) { instance_double(ExternalObject) }
describe '#main_method' do
subject(:main_method) { a.main_method(options) }
let(:options) { { key: :my_key } }
before do
allow(external_obj).to receive(:update_external_attribute)
allow(external_obj).to receive(:external_object_attribute=)
allow(external_obj).to receive(:external_object_attribute)
main_method
end
it 'updates external attribute' do
expect(external_obj)
.to have_received(:update_external_attribute)
.with(reevaluate: true)
.once
end
end
end
和class A
的公共接口。
希望有所帮助。