fea*_*ool 3 unit-testing controller rspec ruby-on-rails
(这个问题与Controller中的Ruby on Rails方法模拟类似,但是它使用的是旧stub语法,而且没有得到正确的答案.)
我想测试我的控制器代码与我的模型代码分开.不应该是rspec代码:
expect(real_time_device).to receive(:sync_readings)
Run Code Online (Sandbox Code Playgroud)
验证是否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
Run Code Online (Sandbox Code Playgroud)
在我的控制器测试中,我想验证(a)是否正在设置@real_time_device并且(b)调用#sync_reading模型方法(但我不想调用模型方法本身,因为它已被模型单元测试).
这是我的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>
Run Code Online (Sandbox Code Playgroud)
当我运行测试时,实际的RealTimeDevice#sync_readings方法被调用,即它试图在我的模型中调用代码.我认为这句话:
expect(real_time_device).to receive(:sync_readings)
Run Code Online (Sandbox Code Playgroud)
是必要的,足以将方法存根并验证它被调用.我怀疑它需要是双倍的.但我无法看到如何使用双倍编写测试.
我错过了什么?
fiv*_*git 10
您正在设置特定实例的期望RealTimeDevice.控制器从数据库中获取记录,但在您的控制器中,它使用的是另一个实例RealTimeDevice,而不是您设置期望的实际对象.
这个问题有两种解决方案.
您可以在以下任何实例上设置期望RealTimeDevice:
expect_any_instance_of(RealTimeDevice).to receive(:sync_readings)
Run Code Online (Sandbox Code Playgroud)
请注意,这不是编写规范的最佳方式.毕竟,这并不能保证您的控制器从数据库中获取正确的记录.
第二个解决方案涉及更多工作,但会导致您的控制器被隔离测试(如果它正在获取实际的数据库记录,则不是这样):
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
Run Code Online (Sandbox Code Playgroud)
有些事情发生了变化.这是发生的事情:
let(:real_time_device) { instance_double(RealTimeDevice) }
Run Code Online (Sandbox Code Playgroud)
总是更喜欢let在您的规范中使用而不是创建局部变量或实例变量.let允许您懒惰地评估对象,它不是在您的规范要求之前创建的.
expect(RealTimeDevice).to receive(:find).with('1').and_return(real_time_device)
Run Code Online (Sandbox Code Playgroud)
数据库查找已存根.我们告诉rSpec确保控制器从数据库中获取正确的记录.重要的是,在规范中创建的测试double的实例将在此处返回.
expect(real_time_device).to receive(:sync_readings)
Run Code Online (Sandbox Code Playgroud)
由于控制器现在使用的是测试双精度而不是实际记录,因此您可以设置测试双精度本身的期望值.
我使用了rSpec 3 instance_double,它验证了该sync_readings方法实际上是由底层类型实现的.这可以防止在缺少方法时传递规范.阅读有关在rSpec文档中验证双精度的更多信息.
请注意,完全不需要在实际ActiveRecord对象上使用测试双精度,但它确实使规范更快.控制器现在也经过完全隔离测试.