我有一个关于如何在示例之间共享rspec-mocks的问题。我正在用rspec-mocks 3.1.3
编写一个新的rails应用程序。我习惯使用旧版本(< 2.14并尝试更新我的知识,如果当前的rspec用法。
我有一个模型方法:
def self.from_strava(activity_id, race_id, user)
@client ||= Strava::Api::V3::Client.new(access_token: 'abc123')
activity = @client.retrieve_an_activity(activity_id)
result_details = {race_id: race_id, user: user}
result_details[:duration] = activity['moving_time']
result_details[:date] = Date.parse(activity['start_date'])
result_details[:comment] = activity['description']
result_details[:strava_url] = "http://www.strava.com/activities/#{activity_id}"
Result.create!(result_details)
end
这是规范:
describe ".from_strava" do
let(:user) { FactoryGirl.build(:user) }
let(:client) { double(:client) }
let(:json_response) { JSON.parse(File.read('spec/support/strava_response.json')) }
before(:each) do
allow(Strava::Api::V3::Client).to receive(:new) { client }
allow(client).to receive(:retrieve_an_activity) { json_response }
allow(Result).to receive(:create!)
end
it "sets the duration" do
expect(Result).to receive(:create!).with(hash_including(duration: 3635))
Result.from_strava('123', 456, user)
end
it "sets the date" do
expect(Result).to receive(:create!).with(hash_including(date: Date.parse("2014-11-14")))
Result.from_strava('123', 456, user)
end
end
当我自己运行单个测试时,它很好,但是当我运行整个describe ".from_strava"
块时,它会失败并显示消息
Double :client 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.
我理解它的含义,但肯定这是在2个例子中使用的double
的恰当用法。毕竟,client
double对示例并不重要,它只是我加载预设响应的一种方式。我想我可以使用WebMock,但这似乎非常低级,并且不能很好地转换为实际编写的代码。毕竟,我们应该只为每个例子断言一件事。
我曾考虑过调用
替换client
allow(Strava::Api::V3::Client).to receive_message_chain(:new, :retrieve_an_activity) { json_response }
但这似乎也不是正确的方法,因为文档说明receive_message_chain
应该是代码气味。
因此,如果我不应该使用receive_message_chain
,共享client
加倍并遵循标准DRY原则,那么我该如何解决这个问题?
我希望得到一些反馈。
谢谢, 戴夫
答案 0 :(得分:8)
当然,这是在2个示例中使用双精度的恰当用法。
不,不是。 :)你正在尝试使用类变量;不要这样做,因为变量不会跨越示例。解决方案是每次都设置客户端,即在每个示例中。
为:
@client ||= Strava::Api::V3::Client.new(access_token: 'abc123')
好:
@client = Strava::Api::V3::Client.new(access_token: 'abc123')
答案 1 :(得分:2)
我不是这个人接受的答案的粉丝。通常真正需要为外部组件缓存客户端(保持活动连接/您可能需要的任何SSL设置等)并且为了解决测试问题而删除它不是理想的解决方案。
为了修复测试(不重构代码),您可以在每次测试后清除实例变量:
after { Result.instance_variable_set("@client", nil) }
虽然不可否认,这不是最干净的解决方案,它似乎是最简单的并且实现了两者,让你有一个清晰的设置,在测试之间没有共享状态,并保持你的客户端缓存在"正常&#34 ;操作模式。
答案 2 :(得分:1)
我在我的应用程序中使用了相同的用例,我们通过将缓存提取到私有方法然后将该方法存根以返回double来解决它(而不是直接对new
方法进行存根)。
例如,在被测试的课程中:
def self.from_strava(activity_id, race_id, user)
activity = strava_client.retrieve_an_activity(activity_id)
...
end
private
def self.strava_client
@client ||= Strava::Api::V3::Client.new(access_token: 'abc123')
end
在规范中:
let(:client) { double(:client) }
before { allow(described_class).to receive(:strava_client).and_return(client) }
...
答案 3 :(得分:1)
TLDR:添加after { order.vendor_service = nil }
以平衡before
块。或者继续阅读...
我遇到了这个问题,而且来自哪里并不明显。在order_spec.rb模型测试中,我有这个:
describe 'order history' do
before do
service = double('VendorAPI')
allow(service).to receive(:order_count).and_return(5)
order.vendor_service = service
end
# tests here ..
end
在我的Order
模型中:
def too_many_orders?
@@vendor_service ||= VendorAPI.new(key: 'abc', account: '123')
return @@vendor_service.order_count > 10
end
当我在order_spec.rb上运行rspec时,这很好用。
我在order_controller_spec.rb中使用allow_any_instance_of()
代替double
和allow
来嘲笑完全不同的东西:
allow_any_instance_of(Order).to receive(:too_many_orders?).and_return(true)
这也测试得很好。
令人困惑的问题是,当我运行全套测试时,我在控制器模拟上得到了OP的错误 - 使用allow_any_instance
的错误。这很难追查,因为问题(或者至少是我的解决方案)存在于我使用double/allow
的模型测试中。
为了解决这个问题,我添加了一个after
块来清除类变量@@ vendor_service,平衡了before
块的操作:
describe 'order history' do
before do
service = double('VendorAPI')
allow(service).to receive(:order_count).and_return(5)
order.vendor_service = service
end
after do
order.vendor_service = nil
end
# tests here ..
end
这迫使||= VendorAPI.new()
在以后不相关的测试中使用真正的new
函数,而不是模拟对象。