Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails 5 Rspec receive with ActionController::Params

I have just upgraded to Rails 5. In my specs I have the following

expect(model).to receive(:update).with(foo: 'bar')

But, since params no longer extends Hash but is now ActionController::Parameters the specs are failing because with() is expecting a hash but it is actually ActionController::Parameters

Is there a better way of doing the same thing in Rspec such as a different method with_hash?

I can get around the issue using

expect(model).to receive(:update).with(hash_including(foo: 'bar'))

But that is just checking if the params includes that hash, not checking for an exact match.

like image 613
Cjmarkham Avatar asked Sep 26 '16 12:09

Cjmarkham


2 Answers

You could do:

params = ActionController::Parameters.new(foo: 'bar')
expect(model).to receive(:update).with(params)

However it still smells - you should be testing the behaviour of the application - not how it does its job.

expect {
  patch model_path(model), params: { foo: 'bar' }
  model.reload
}.to change(model, :foo).to('bar')

This is how I would test the integration of a controller:

require 'rails_helper'
RSpec.describe "Things", type: :request do
  describe "PATCH /things/:id" do

    let!(:thing) { create(:thing) }
    let(:action) do
      patch things_path(thing), params: { thing: attributes }
    end

    context "with invalid params" do
      let(:attributes) { { name: '' } }
      it "does not alter the thing" do
         expect do 
           action 
           thing.reload
         end.to_not change(thing, :name)
         expect(response).to have_status :bad_entity
      end
    end

    context "with valid params" do
      let(:attributes) { { name: 'Foo' } }
       it "updates the thing" do
         expect do 
           action 
           thing.reload
         end.to change(thing, :name).to('Foo')
         expect(response).to be_successful
      end
    end
  end
end

Is touching the database in a spec inheritenly bad?

No. When you are testing something like a controller the most accurate way to test it is by driving the full stack. If we in this case had stubbed out @thing.update we could have missed for example that the database driver threw an error because we where using the wrong SQL syntax.

If you are for example testing scopes on a model then a spec that stubs out the DB will give you little to no value.

Stubbing may give you a fast test suite that is extremely brittle due to tight coupling and that lets plenty of bugs slip through the cracks.

like image 159
max Avatar answered Oct 16 '22 01:10

max


I handled this by creating in spec/rails_helper.rb

def strong_params(wimpy_params)
  ActionController::Parameters.new(wimpy_params).permit!
end

and then in a specific test, you can say:

expect(model).to receive(:update).with(strong_params foo: 'bar')

It's not much different from what you're already doing, but it makes the awkward necessity of that extra call a little more semantically meaningful.

like image 11
Steve Avatar answered Oct 16 '22 01:10

Steve