Let’s say we were creating an API that allows us to create ‘foo’s. Foos, as far as we know, will have a first name and a title. We can write a request spec to help us drive out our endpoint’s behavior:
require 'rails_helper'
describe 'the foo resource' do
it 'allows foos to be created' do
foo = {
first_name: 'Jeff',
title: 'Assistant'
}
post '/foos',
foo.to_json,
{'CONTENT_TYPE' => 'application/json', 'HTTP_ACCEPT' => 'application/json'}
get '/foos',
{},
{'HTTP_ACCEPT' => 'application/json'}
foos = JSON.parse(response.body)
expect(foos.last['first_name'].to eq('Jeff'))
expect(foos.last['title']).to eq('Assistant')
end
end
We are in the midst of driving out this feature, and find ourselves with a pretty standard Rails controller:
class FoosController < ApplicationController
respond_to :json
def index
respond_with Foo.all.entries
end
def create
foo = Foo.new(foo_params)
foo.save
respond_with foo, location: nil
end
private
def foo_params
params.require(:foo).permit(:first_name)
end
end
We run our request spec again, and see that it is failing for the following reason:
expected: "Assistant"
got: nil
(compared using ==)
./spec/requests/foos_spec.rb:22:in `block (2 levels) in '
-e:1:in `load'
-e:1:in `'
1 example, 1 failure, 0 passed
Right off the bat, it’s not entirely obvious what we’ve missed. Did we forget to do something in the database? In the model? In the controller? While finding the issue is not “Where’s Waldo”-tier, our test is providing us with some pretty weak feedback. Given that this is an integration test, the problem is that we’ve most likely wired something up incorrectly. If we check our test.log, hidden in between the request and our database logs is the culprit:
Started POST "/foos" for 127.0.0.1 at 2014-08-22 16:37:45 -0700
Processing by FoosController#create as JSON
Parameters: {"first_name"=>"Jeff", "title"=>"Assistant", "foo"=>{"first_name"=>"Jeff", "title"=>"Assistant"}}
Unpermitted parameters: title
As we are oft to do, we forgot to whitelist the :title
parameter, and so the parameter is being completely ignored:
def foo_params
params.require(:foo).permit(:first_name)
Ideally our test would tell us that we’ve made a mistake wiring up our code, rather than the ambiguous error we’re getting. We can make our tests give us better feedback by reconfiguring Rails to throw an error when given unauthorized attributes. In config/environments/test.rb
we can add the line:
Rails.application.configure do
... # some other config
config.action_controller.action_on_unpermitted_parameters = :raise
... # some other config
end
Now, when we run our request spec, we get this error:
ActionController::UnpermittedParameters: found unpermitted parameters: title
./app/controllers/foos_controller.rb:17:in `foo_params'
./app/controllers/foos_controller.rb:9:in `create'
./spec/requests/foos_spec.rb:11:in `block (2 levels) in '
-e:1:in `load'
-e:1:in `'
1 example, 1 failure, 0 passed
Our test failure reveals the exact line in our code where we’ve misconfigured our app. In the future, it will be obvious when we inevitably make this mistake again. Further, this change will highlight places in our (tested) code where we’ve been sending parameters that haven’t been whitelisted.
About the Author