pmichna
pmichna

Reputation: 4888

How to test controller without hitting the database?

I have a controller:

class InvoicesController < ApplicationController
  def edit
    @invoice = Invoice.includes(:client, :document_status).find(params[:id])
    return head :forbidden unless @invoice.editable?
  end
end

I want to write a test but without hitting the database:

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

Is there a better approach? I don't like this chain of allows but I can't think of anything better. Setting the @invoice instance variable in the test would be bad, because then I would rely on the implementation.

I could obviously just create the invoice in the DB and don't bother about all the mocks and stubs. However, doing this in all the specs would slow down the whole test suite.

Upvotes: 0

Views: 109

Answers (1)

Paweł Dąbrowski
Paweł Dąbrowski

Reputation: 754

You can move the query part to the query object so then you can have this code:

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

then you can easily test your controller without hitting database

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

Is recommended to test InvoiceQuery with database as it's query object and it performs calls to database directly

Upvotes: 1

Related Questions