如何在不命中数据库的情况下测试控制器?

时间:2016-12-09 15:06:16

标签: ruby-on-rails rspec

我有一个控制器:

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实例变量会很糟糕,因为那时我会依赖于实现。

我显然可以在数据库中创建发票,而不用担心所有的模拟和存根。但是,在所有规范中执行此操作会降低整个测试套件的速度。

1 个答案:

答案 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作为查询对象,并直接执行对数据库的调用