Skip to content

Instantly share code, notes, and snippets.

@jeremyw
Created September 29, 2012 21:25
Show Gist options
  • Save jeremyw/3805223 to your computer and use it in GitHub Desktop.
Save jeremyw/3805223 to your computer and use it in GitHub Desktop.
Testing Rails 4 strong parameters
class AccountsController < ApplicationController
def update
@account = Account.find(params[:id])
respond_to do |format|
if @account.update_attributes(account_params)
format.html { redirect_to @account, notice: 'Account was successfully updated.' }
else
format.html { render action: "edit" }
end
end
end
private
def account_params
# note that the Account#name attribute is permitted, but Account#balance is not
params.require(:account).permit(:name)
end
end
require 'spec_helper'
describe AccountsController do
describe "PUT update" do
describe "with forbidden params" do
let(:account) { Account.create! balance: 100.0, name: 'Checking' }
it "does not update the forbidden params" do
put :update,
id: account.to_param,
account: { 'name' => 'Savings', 'balance' => '1000000' }
assigns(:account).name.should eq('Savings') # explicitly permitted
assigns(:account).balance.should eq(100.0) # implicitly forbidden
response.should redirect_to account
end
end
end
end

This is a simple example of strong parameters in Rails (Rails 4 or Rails 3.2 with the strong_parameters gem). The test demonstrates that the Account's name attribute can be updated via the update action, but its balance cannot. The system works.

However, how far should we take these kinds of tests? Now that we've moved this responsibility from the model to the controller, should we invest in controller specs/tests to:

  1. ensure that all permitted attributes can be updated?
  2. ensure that any forbidden attributes cannot be updated?

Is there a good, clean, expressive way to test the private account_params method in the controller, to verify the contents of the whitelist rather than the effects of the whitelist? I've used shoulda-style "should(_not) allow_mass_assignment_of" macros in the past. There doesn't appear to be anything analogous for strong_parameters yet.

@freerobby
Copy link

Disclaimer: I haven't played with Rails 4 for or the strong_parameters gem; I'm inferring how it works from your example.

I think you're testing the functionality of a railsism, when what is more desirable is to test that your application properly implements it.

An older, analogous example:

class User < ActiveRecord::Base
  has_many :friends
end

Do you write tests to confirm that Friend objects get created with the correct foreign key when attached to a user? I don't because I consider this the job of ActiveRecord and I know that functionality is well-tested there. Instead, I use mocks and stubs to verify that I've implemented the features I want to implement. If my goal is to prevent that line from accidentally getting deleted, I might use:

require 'remarkable'
describe User do
  it {should have_many(:friends)}
end

In your example, I might try this:

describe AccountsController do
  describe "params handling" do
    it ":account is required" do
      params.should_receive(:require).with(:account)
      controller.send :account_params
    end
    it ":name is permitted" do
      params.should_receive(:permit).with(:name)
      controller.send :account_params
    end
    it ":balance is not permitted" do
      params.should_not_receive(:permit).with(:balance)
      controller.send :account_params
    end
  end
end

@jeremyw
Copy link
Author

jeremyw commented Sep 29, 2012

@freerobby Good point, I absolutely do not want to test Rails in my app's tests. The expectation syntax is pretty noisy and not very expressive, but a little DSL/macro could wrap it pretty easily I think.

@silverdr
Copy link

Hello, would it be possible to update this for Rails 5, and its 'test_helper'?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment