[英]mocking controller methods in rspec
(This question is similar to Ruby on Rails Method Mocks in the Controller , but that was using the old stub
syntax, and besides, that didn't receive a working answer.) (这个问题类似于Controller中的Ruby on Rails方法模拟 ,但这是使用旧的stub
语法,而且,它没有收到有效的答案。)
I want to test my controller code separate from my model code. 我想测试我的控制器代码与我的模型代码分开。 Shouldn't the rspec code: 不应该是rspec代码:
expect(real_time_device).to receive(:sync_readings)
verify that RealTimeDevice#sync_readings
gets called, but inhibit the actual call? 验证RealTimeDevice#sync_readings
被调用,但是禁止实际调用?
My controller has a #refresh method that calls RealTimeDevice#sync_readings
: 我的控制器有一个#refresh方法调用RealTimeDevice#sync_readings
:
# app/controllers/real_time_devices_controller.rb
class RealTimeDevicesController < ApplicationController
before_action :set_real_time_device, only: [:show, :refresh]
<snip>
def refresh
@real_time_device.sync_readings
redirect_to :back
end
<snip>
end
In my controller tests, I want to verify that (a) that @real_time_device is being set up and (b) the #sync_reading model method is getting called (but I don't want to invoke the model method itself since that's covered by the model unit tests). 在我的控制器测试中,我想验证(a)是否正在设置@real_time_device并且(b)调用#sync_reading模型方法(但我不想调用模型方法本身,因为它已被模型单元测试)。
Here's my controller_spec code that doesn't work: 这是我的controller_spec代码不起作用:
# file: spec/controllers/real_time_devices_controller_spec.rb
require 'rails_helper'
<snip>
describe "PUT refresh" do
it "assigns the requested real_time_device as @real_time_device" do
real_time_device = RealTimeDevice.create! valid_attributes
expect(real_time_device).to receive(:sync_readings)
put :refresh, {:id => real_time_device.to_param}, valid_session
expect(assigns(:real_time_device)).to eq(real_time_device)
end
end
<snip>
When I run the test, the actual RealTimeDevice#sync_readings
method is getting called, ie, it's trying to call code in my model. 当我运行测试时,实际的RealTimeDevice#sync_readings
方法被调用,即它试图在我的模型中调用代码。 I thought the line: 我认为这句话:
expect(real_time_device).to receive(:sync_readings)
was necessary and sufficient to stub the method and verify that it got called. 是必要的,足以将方法存根并验证它被调用。 My suspicion is that it needs to be a double. 我怀疑它需要是双倍的。 But I can't see how to write the test using a double either. 但我无法看到如何使用双倍编写测试。
What am I missing? 我错过了什么?
You're setting an expectation on a specific instance of RealTimeDevice
. 您正在为RealTimeDevice
的特定实例设置期望。 The controller fetches the record from the database, but in your controller, it's using another instance of RealTimeDevice
, not the actual object you set the expectation on. 控制器从数据库中获取记录,但在您的控制器中,它使用的是RealTimeDevice
的另一个实例,而不是您设置期望的实际对象。
There are two solutions to this problem. 这个问题有两种解决方案。
You can set an expectation on any instance of RealTimeDevice
: 您可以在RealTimeDevice
任何实例上设置期望:
expect_any_instance_of(RealTimeDevice).to receive(:sync_readings)
Note that this is not the best way to write your spec. 请注意,这不是编写规范的最佳方式。 After all, this doesn't guarantee that your controller fetches the right record from the database. 毕竟,这并不能保证您的控制器从数据库中获取正确的记录。
The second solution involves a bit more work, but will cause your controller to be tested in isolation (which it is not really if it's fetching actual database records): 第二个解决方案涉及更多的工作,但会导致您的控制器被隔离测试(如果它正在获取实际的数据库记录,它实际上并不是这样):
describe 'PUT refresh' do
let(:real_time_device) { instance_double(RealTimeDevice) }
it 'assigns the requested real_time_device as @real_time_device' do
expect(RealTimeDevice).to receive(:find).with('1').and_return(real_time_device)
expect(real_time_device).to receive(:sync_readings)
put :refresh, {:id => '1'}, valid_session
expect(assigns(:real_time_device)).to eq(real_time_device)
end
end
Quite some things have changed. 有些事情发生了变化。 Here's what happens: 这是发生的事情:
let(:real_time_device) { instance_double(RealTimeDevice) }
Always prefer using let
in your specs rather than creating local variables or instance variables. 总是更喜欢在规范中使用let
而不是创建局部变量或实例变量。 let
allows you to lazy evaluate the object, it's not created before your spec requires it. let
允许你懒惰地评估对象,它不是在你的规范要求之前创建的。
expect(RealTimeDevice).to receive(:find).with('1').and_return(real_time_device)
The database lookup has been stubbed. 数据库查找已存根。 We're telling rSpec to make sure that the controller fetches the correct record from the database. 我们告诉rSpec确保控制器从数据库中获取正确的记录。 The important part is that the very instance of the test double created in the spec is being returned here. 重要的是,在规范中创建的测试double的实例将在此处返回。
expect(real_time_device).to receive(:sync_readings)
Since the controller is now using the test double rather than an actual record, you can set expectations on the test double itself. 由于控制器现在使用的是测试双精度而不是实际记录,因此您可以设置测试双精度本身的期望值。
I've used rSpec 3's instance_double
, which verifies the sync_readings
method is actually implemented by the underlying type. 我使用了rSpec 3的instance_double
,它验证了sync_readings
方法实际上是由底层类型实现的。 This prevents specs from passing when a method would be missing. 这可以防止在缺少方法时传递规范。 Read more about verifying doubles in the rSpec documentation . 阅读有关在rSpec文档中验证双精度的更多信息 。
Note that it's not required at all to use a test double over an actual ActiveRecord
object, but it does make the spec much faster. 请注意,完全不需要在实际的ActiveRecord
对象上使用测试双ActiveRecord
,但它确实使规范更快。 The controller is now also tested in complete isolation. 控制器现在也经过完全隔离测试。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.