我有一个控制器:
class InvoicesController < ApplicationController
def edit
@invoice = Invoice.includes(:client, :document_status).find(params[:id])
return head :forbidden unless @invoice.editable?
end
end
我想写一个测试,但没有点击数据库:
describe InvoicesController do
describe '#edit' do
let(:invoice_id) { '1' }
let(:invoice) { double(Invoice, editable?: false) }
let(:invoice_includes) { double }
before do
allow(invoice_includes).to receive(:find).with(invoice_id) { invoice }
allow(Invoice).to receive(:includes).with(:client, :document_status) { invoice_includes }
end
subject { get :edit, params: {id: invoice_id} }
it { is_expected.to have_http_status(403) }
end
end
有更好的方法吗?我不喜欢这条allow
链,但我想不出更好的东西。在测试中设置@invoice
实例变量会很糟糕,因为那时我会依赖于实现。
我显然可以在数据库中创建发票,而不用担心所有的模拟和存根。但是,在所有规范中执行此操作会降低整个测试套件的速度。
答案 0 :(得分:1)
您可以将查询部分移动到查询对象,然后您可以使用以下代码:
class InvoiceQuery
def find(id)
::Invoice.includes(:client, :document_status).find(id)
end
end
class InvoicesController < ApplicationController
def edit
@invoice = invoice_query.find(params[:id])
return head :forbidden unless @invoice.editable?
end
private
def invoice_query
::InvoiceQuery.new
end
end
然后您可以轻松测试您的控制器而无需访问数据库
describe InvoicesController do
describe '#edit' do
let(:invoice_query) do
instance_double(InvoiceQuery, find: double)
end
let(:invoice)
instance_double(Invoice, editable?: false)
end
let(:invoice_id) { '1' }
before do
allow(InvoiceQuery).to receive(:new).and_return(invoice_query)
allow(invoice_query).to receive(:find).with(invoice_id).and_return(invoice)
end
subject { get :edit, params: {id: invoice_id} }
it { is_expected.to have_http_status(403) }
end
end
建议使用数据库测试InvoiceQuery
作为查询对象,并直接执行对数据库的调用