你打破了MyPolicy.new
。
您的包装器new
不会返回新的 MyPolicy 对象。它返回的结果expect(method.call(*args)).to receive(:show?).and_call_original
是 MessageExpectation。
相反,您可以确保新对象以tap
.
# This is an allow. It's not a test, it's scaffolding for the test.
allow(MyPolicy).to receive(:new)
.and_wrap_original do |method, *args|
method.call(*args).tap do |obj|
expect(obj).to receive(:show?).and_call_original
end
end
或者用老式的方式来做。
allow(MyPolicy).to receive(:new)
.and_wrap_original do |method, *args|
obj = method.call(*args)
expect(obj).to receive(:show?).and_call_original
obj
end
将这两个步骤分开通常更简单。模拟 MyPolicy.new 以返回特定对象,然后期望调用显示?在那个物体上。
let(:policy) do
# This calls the real MyPolicy.new because policy is referenced
# when setting up the MyPolicy.new mock.
MyPolicy.new
end
before do
allow(MyPolicy).to receive(:new).and_return(policy)
end
it 'shows' do
expect(policy).to receive(:show?).and_call_original
MyPolicy.new.show?
end
这确实意味着 MyPolicy.new 总是返回相同的对象。这是测试的优势,但可能会破坏某些东西。这更灵活,因为它将脚手架与正在测试的东西分开。脚手架可以重复使用。
RSpec.describe SomeClass do
let(:policy) {
MyPolicy.new
}
let(:thing) {
described_class.new
}
shared_context 'mocked MyPolicy.new' do
before do
allow(MyPolicy).to receive(:new).and_return(policy)
end
end
describe '#some_method' do
include_context 'mocked new'
it 'shows a policy' do
expect(policy).to receive(:show?).and_call_original
thing.some_method
end
end
describe '#other_method' do
include_context 'mocked MyPolicy.new'
it 'checks its policy' do
expect(policy).to receive(:check)
thing.other_method
end
end
end
最后,不可访问的构造函数调用对于测试来说都是一个令人头疼的问题,而且它们不灵活。这是一个不能被覆盖的默认值。
class SomeClass
def some_method
MyPolicy.new.show?
end
end
将其变成具有默认值的访问器。
class SomeClass
attr_writer :policy
def policy
@policy ||= MyPolicy.new
end
def some_method
policy.show?
end
end
现在可以在测试中或其他任何地方访问它。
RSpec.describe SomeClass do
let(:thing) {
described_class.new
}
describe '#some_method' do
it 'shows its policy' do
expect(thing.policy).to receive(:show?).and_call_original
thing.some_method
end
end
end
这是最稳健的选择。