UPDATE: Thanks to fellow Pivots Alex Kwiatkowski and Rick Reilly, we found that inheriting from ActionController::Parameters
didn’t work for update_attribtues
. Alex explains some of the changes they made. In the mean time, check out my repo for the example, including a commit for the failing test and the fix.
Since Rails 4 has been released we’ve been getting involved and learning about what has changed. One of the changes most discussed is strong parameters and I wanted to explore some ideas about how to test this new feature. Strong parameters are a way of white listing HTTP query parameters and moves the burden of whitelisting from the ActiveModel/ActiveRecord classes and into the controllers. I think this feels like a better place for this to happen.
Most examples I’ve seen encourage the following:
class UsersController < ApplicationController
def create
User.create(user_params)
end
private
def user_params
params.require(:user).permit(:name)
end
end
However I think this encourages asking the question of how defensive to test the strong parameters portion of this. Should you do the defensive code for each action or is one enough?
describe "#create" do
it 'creates a user' do
User.should_receive(:create).
with({name: 'Sideshow Bob'}.with_indifferent_access)
post :create, user:
{ first_name: 'Sideshow', last_name: 'Bob', name: 'Sideshow Bob' }
end
end
There are a few ways to extract the parameterisation whilst making it testable but the one which feels most natural for Rails is to extend the ActionController::Parameters
class where it can be tested independently of the controller actions.
My first attempt lead me one way but I eventually ended up extending the ActionController::Parameters, but as Alex pointed out this doesn’t work for update_attributes
class UsersController < ApplicationController
class UserParams < ActionController::Parameters
def initialize params
filtered_params = params.
require(:user).
permit(:name)
super(filtered_params)
end
end
def create
User.create(UserParams.new(params))
end
end
I went back to a previous thought and made the params call a class method.
class UsersController < ApplicationController
class UserParams
def build params
params.require(:user).permit(:name)
end
end
def create
User.create(UserParams.build(params))
end
end
Now the strong parameter aspects can be tested independently
describe UsersController::UserParams do
it 'cleans the params' do
params = ActionController::Parameters.new(
user: {foo: 'bar', name: 'baz'})
user_params = UsersController::UserParams.build(params)
expect(user_params).to eq({name: 'baz'}.with_indifferent_access)
end
end
The controller can then choose it’s interaction with the UsersController::UserParams
class through strong (which requires knowing the controller and action) or weak stubbing:
describe "#create" do
let(:http_params) do
{ user:
{ first_name: 'Sideshow', last_name: 'Bob', name: 'Sideshow Bob' }}
end
let(:model_params) { double(:model_params) }
before do
UsersController::UserParams.stub(:build) { model_params }
end
it 'creates a user with strong stubbing' do
UsersController::UserParams.stub(:build).
with(
http_params.merge(controller: 'users', action: 'create').
with_indifferent_access) { model_params }
User.should_receive(:create).with(model_params)
post :create, http_params
end
it 'creates a user with weak stubbing' do
User.should_receive(:create).with(model_params)
post :create, http_params
end
end
This now allows the tests to focus on the business logic of the actions rather than worrying about the defensive coding of strong parameters and also gives the developer confidence that the strong parameters are adequately tested.
The evolution of this can be seen in this repository, with thanks to Bryan Helmkamp and Alex Kwiatkowski.