也许,会有太多代码,但我想理解为什么RSpec以这种方式工作。
我正在尝试double
与远程REST API交互的外部对象。
示例代码:
class App
class << self
def foo
external.func1
end
def bar
external.func2
end
def external
@external ||= ExternalObject.new
end
end
end
class ExternalObject
def func1
puts 'func1'
end
def func2
puts 'func2'
end
end
此规范有效:
require 'rspec'
require_relative 'app'
describe App do
describe '.foo' do
it 'calls func1' do
expect_any_instance_of(ExternalObject).to receive(:func1)
described_class.foo
end
end
describe '.bar' do
it 'calls func2' do
expect_any_instance_of(ExternalObject).to receive(:func2)
described_class.bar
end
end
end
但这不起作用:
require 'rspec'
require_relative 'app'
describe App do
let(:external) { double('ExternalObject', func1: 1, func2: 2) }
before(:each) do
allow(ExternalObject).to receive(:new).and_return(external)
end
describe '.foo' do
it 'calls func1' do
expect(external).to receive(:func1)
described_class.foo
end
end
describe '.bar' do
it 'calls func2' do
expect(external).to receive(:func2)
described_class.bar
end
end
end
当执行第二个示例时,RSpec输出看起来像double
不再包含存根:
1) App.bar calls func2
Failure/Error: described_class.bar
Double "ExternalObject" received unexpected message :func2 with (no args)
如果将App.external
中的代码替换为:
@external = ExternalObject.new
一切顺利。
我搜索了该错误的原因,但没有找到任何内容。
更新
另外,为了防止类的记忆,可以克隆对象。也许这不是一个好主意,但它确实有效。
require 'rspec'
require_relative 'app'
describe App do
let(:external) { double('ExternalObject', func1: 1, func2: 2) }
before(:each) do
stub_const('App', App.clone)
allow(ExternalObject).to receive(:new).and_return(external)
end
describe '.foo' do
it 'calls func1' do
expect(external).to receive(:func1)
App.foo
end
end
describe '.bar' do
it 'calls func2' do
expect(external).to receive(:func2)
App.bar
end
end
end
更新2
我具体化了我的问题:为什么double
在@external
之间在测试之间进行了记忆,但是没有存根方法?
答案 0 :(得分:0)
因为let
为每个示例创建了一个新的双精度。
尝试使用最新的(3.6)rspec运行此规范,您将收到以下错误:
1) App.bar calls func2
Failure/Error: external.func2
#<Double "ExternalObject"> was originally created in one example but has leaked into another example and can no longer be used. rspec-mocks' doubles are designed to only last for one example, and you need to create a new one in each example you wish to use it for.
这是因为在类级别上对象的记忆。
当第一个示例运行时,let
会创建第一个具有第一个期望的double,并且会被记忆。
在下一个示例中,let
创建一个新的double,并且期望值设置在该新的double上,但是当测试代码调用described_class.external
时,它会收到第一个memoized test double而不是新的期望。
您可以尝试参阅以下示例:
RSpec.describe App do
let(:external) { double('ExternalObject', func1: 1, func2: 2) }
before { allow(ExternalObject).to receive(:new).and_return(external) }
describe '.foo' do
it 'calls func1' do
p external.object_id # => 70184943614340
p described_class.external.object_id # => 70184943614340
expect(external).to receive(:func1)
described_class.foo
end
end
describe '.bar' do
it 'calls func2' do
p external.object_id # => 70184942937140
p described_class.external.object_id # => 70184943614340
expect(external).to receive(:func2)
described_class.bar
end
end
end