LoginSignup
2
3

More than 5 years have passed since last update.

[RSpec] ネストしたルーティングのPOST, PUT(PATCH)のテストが大変だったというお話

Last updated at Posted at 2019-01-27

こんなコントローラがありました

class CardsController < ApplicationController
  before_action :set_card, only: %i(show edit update destroy)
  before_action :set_list, only: %i(new create)
  def new
    @card = Card.new
  end

  def create
    @card = Card.new(card_params)
    if @card.save
      redirect_to :root
    else
      render :new
    end
  end

  (中略)

  private

  def card_params
    params.require(:card).permit(:title, :memo, :list_id)
  end

  def set_card
    @card = Card.find_by(id: params[:id])
  end

  def set_list
    @list = List.find_by(id: params[:list_id])
  end
end

こんなルーティングを設定しました

  resources :lists,       except: %i(index, show) do
    resources :cards,     except: :index
  end

するとこうなりました

    list_cards POST   /lists/:list_id/cards(.:format)            cards#create
 new_list_card GET    /lists/:list_id/cards/new(.:format)        cards#new
edit_list_card GET    /lists/:list_id/cards/:id/edit(.:format)   cards#edit
     list_card GET    /lists/:list_id/cards/:id(.:format)        cards#show
               PATCH  /lists/:list_id/cards/:id(.:format)        cards#update
               PUT    /lists/:list_id/cards/:id(.:format)        cards#update
               DELETE /lists/:list_id/cards/:id(.:format)        cards#destroy

Request Specを書きました

RSpec.describe "CardsResponses", type: :request do
  describe "cards responses" do
    let(:user)            { create(:user01) }
    let(:list)            { create(:list01, user: user) }
    let(:card)            { create(:card01, list: list) }
    let(:valid_params)    { attributes_for(:card01, list: list, title: "CARD000") }

    (中略)

    describe "#create" do
      it "responds [302]" do
        post list_cards_path(card), params: { card: valid_params }
        expect(response).to have_http_status 302
      end
    end
  end
end

結果

  1) CardsResponses cards responses as a login user #create responds [302]
     Failure/Error: expect(response).to have_http_status 302
       expected the response to have status code 302 but it was 200
     # ./spec/requests/cards_responses_spec.rb:31:in `block (5 levels) in <main>'
     # -e:1:in `<main>'

成功してredirect_to :rootに飛ばされるはずが、
失敗してrender :editされてしまっている。

つまりは、createに失敗している。

対処

createのテストコードにbinding.pryをはさみました

    describe "#create" do
      it "responds [302]" do
        post list_cards_path(card), params: { card: valid_params }
        binding.pry
        expect(response).to have_http_status 302
      end
    end

(仮定1) list_cards_pathに与える変数が間違っている?

letで設定していたのは以下。

    let(:user)            { create(:user01) }
    let(:list)            { create(:list01, user: user) }
    let(:card)            { create(:card01, list: list) }

POST list_cards_path /lists/:list_id/cards(.:format)なので、
パラメータとして、:list_idが求められている。

[3] pry(#<RSpec::ExampleGroups::CardsResponses::CardsResponses::AsALoginUser::Create>)
> list_cards_path(card)
=> "/lists/1/cards"

あってる?

[4] pry(#<RSpec::ExampleGroups::CardsResponses::CardsResponses::AsALoginUser::Create>)
> list_cards_path(list, card)
=> "/lists/1/cards.1"

おっとこれはおかしい

[5] pry(#<RSpec::ExampleGroups::CardsResponses::CardsResponses::AsALoginUser::Create>)
> list_cards_path(list)
=> "/lists/1/cards"

あってる

list has_many cardsの関係なので、お互いのidは関連付けで持ってるから、
どうやらlist、cardどちら指定してもOKみたい。
今回はcardにしておく

(仮定2) 与えてるパラメータが間違っている?

そもそもOKなパラメータって何よ?

cards_controller
  private

  def card_params
    params.require(:card).permit(:title, :memo, :list_id)
  end

title, memo, list_idね。

に対して、僕が設定したパラメータはこんなんでした。

    let(:valid_params)    { attributes_for(:card01, list: list, title: "CARD000") }

factory.rbに登録してあるcard01の関連付けにlistを設定、titleも変えてcard01を呼び出す。

これをpry上で実行すると

[7] pry(#<RSpec::ExampleGroups::CardsResponses::CardsResponses::AsALoginUser::Create>)
> valid_params
=> {:title=>"CARD000",
 :memo=>"memo01",
 :list=>
  #<List:0x00007f8d4f1c32c8
   id: 1,
   title: "todo01",
   user_id: 1,
   created_at: Sun, 27 Jan 2019 02:24:20 UTC +00:00,
   updated_at: Sun, 27 Jan 2019 02:24:20 UTC +00:00>}

ほーん

require.permitで受け取るパラメータは参照できるのかね?

[8] pry(#<RSpec::ExampleGroups::CardsResponses::CardsResponses::AsALoginUser::Create>)
> valid_params[:title]
=> "CARD000"

できる

[9] pry(#<RSpec::ExampleGroups::CardsResponses::CardsResponses::AsALoginUser::Create>)
> valid_params[:memo]
=> "memo01"

できる

[10] pry(#<RSpec::ExampleGroups::CardsResponses::CardsResponses::AsALoginUser::Create>)
> valid_params[:list_id]
=> nil

!!!!!!!!!!!!!!

できない…

(仮定2のつづき) 関連付けをlist: listで指定したのが間違っていた?

てなわけで、valid_parmas[:list_id]で参照できるように修正してみる。

(修正前)

    let(:valid_params)    { attributes_for(:card01, list: list, title: "CARD000") }

(修正後)

    let(:valid_params)    { attributes_for(:card01, list_id: list.id, title: "CARD000") }

結果

[1] pry(#<RSpec::ExampleGroups::CardsResponses::CardsResponses::AsALoginUser::Create>)
> valid_params
=> {:title=>"CARD000", :memo=>"memo01", :list_id=>1}

お!ちょっと変わった。
:list_idもしっかり設定されてる。

でも本当に参照できる?

[2] pry(#<RSpec::ExampleGroups::CardsResponses::CardsResponses::AsALoginUser::Create>)
> valid_params[:title]
=> "CARD000"

できる

[3] pry(#<RSpec::ExampleGroups::CardsResponses::CardsResponses::AsALoginUser::Create>)
> valid_params[:memo]
=> "memo01"

できる



肝心のlist_idは?



[4] pry(#<RSpec::ExampleGroups::CardsResponses::CardsResponses::AsALoginUser::Create>)
> valid_params[:list_id]
=> 1



できる!!!!!!!

てなわけで

pryを終了

Finished in 1.26 seconds (files took 0.38429 seconds to load)
50 examples, 0 failures, 2 pending

やっと通りました〜

結論

POST, PUT, PATCH用のパラメータを設定するとき、
コントローラのpermit.requireで指定しているパラメータを
(pry上で)きちんと参照できる形で設定しないと
弾かれてしまうので注意しないといけない



おわり

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3