RSpec3 & FactoryGirl & StrongParametersでハマった

RSpec3とFactoryGirlを使ってControllerのテストを作っている時にStrongParametersで苦戦を強いられたのでメモ。

先にまとめると

  • FactoryGirlのattributes_for()でフォーム入力データを晴々するときは、本当にフォームから渡すキー以外は削れ
  • StrongParametersは入れ子のハッシュデータに対して同階層での網羅性をチェックするので無駄なpermitを行うな

環境

現象

コントローラーのテストで POST #create のテストを書いていたとき、ActionController::UnpermittedParametersの例外が飛んでテストが成功しない。

前提&想定

Hogeクラスの属性は:id(Integer)、:fuga_id(Integer)、 :title(String)、:text(String)とする。fuga_idは外部キーなので入力フォームからの変更を行わない。

HogesControllerの#create

def create
 # first_idとsecond_idを使って何かする。
 nanka_function(params[:first_id])
 @fuga = Fuga.find_by(:id params[:second_id])
 @hoge = Hoge.new(hoge_params)
 @hoge.fuga_id = @fuga.id
 @hoge.save
 redirect_to(hoges_path)
end

def hoge_params
   params.require(:hoge).permit(:title, :text)
end

上記のテストコード

it "新しいHogeオブジェクトが作成される。" do
        hoge_params = attributes_for(:hoge)
        expect{
          post :create, first_id: @first_id, second_id: @second_id, hoge: hoge_params
        }.to change(Hoge, :count).by(1)
      end

期待している入力フォームからのデータ

{ first_id: "1", second_id: "2",  hoge: { title: "New Title", text: "New Text."}}

StrongParametersに関するエラーの見逃しを防ぐため例外が飛ぶようにしている. config/environments/test.rbで以下を設定。

 config.action_controller.action_on_unpermitted_parameters = :raise

原因その1:FactoryGirl#attributes_for()で作ったハッシュの中に想定外のキーが含まれている

spec/factories/hoges.rb にて以下のように定義していた。

FactoryGirl.define do
  factory :hoge do
    fuga_id 2
    title "New Title"
    text "New Text."
  end
end

その結果、attributes_for(:hoge) で生成されるハッシュが以下のようになり、想定と違うものになってしまった。

{fuga_id: "2", title: "New Title", text: "New Text."}

このため、#createに渡されるパラメーターは以下のように「fuga_id: "2"」が余分に付け加えられる。

{ first_id: "1", second_id: "2",  hoge: {fuga_id: "2", title: "New Title", text: "New Text."}}

その結果「found unpermitted parameters fuga_id」とエラーがでて、例外も飛ぶ。

入力フォームで渡す予定のない要素は削っておかないといけない。テストコードを以下のように変える。

it "新しいHogeオブジェクトが作成される。" do
        hoge_params = attributes_for(:hoge)
        hoge_params.delete(:fuga_id) ## この行を追加
        expect{
          post :create, first_id: @first_id, second_id: @second_id, hoge: hoge_params
        }.to change(Hoge, :count).by(1)
      end

原因その2:中途半端にpermitする

試行錯誤しているうちに以下のように#createを以下のように変更してしまう。

def create
 nanka_function(params.permit(:first_id)) ## :first_idだけpermitしてしまう
 @fuga = Fuga.find_by(:id params[:second_id])
 @hoge = Hoge.new(hoge_params)
 @hoge.fuga_id = @fuga.id
 @hoge.save
 redirect_to(hoges_path)
end

その結果、原因1が解決しているのに「found unpermitted parameters second_id, hoge」のエラーがでる。StrongParametersはハッシュの入れ子の度合いで階層ごとに許可の有無をみている様子。:first_idがpermitの対象になったので、以下のハッシュでいう同階層の:second_idと:hogeがチェックの対象になって上記のエラーがでた様子。

{ first_id: "1", second_id: "2",  hoge: {title: "New Title", text: "New Text."}}

素直に「params.permit(:first_id)」を「params[:first_id]」に戻せば問題解決。

おわりに

相当ハマった。