🔥

リクエストスペックで`GET #edit`をテストして詰まったときの話

2022/05/25に公開

概要

リクエストスペックの GET #edit をテストする際に結構詰まったので、ここに備忘録として残す。
具体的に言うと、HTTPステータスコードの検証で[200(:ok)]を期待したが、結果は[302(:found)]として一覧ページにリダイレクトされてしまっていた。
今回は、GET #editに絞って記載する。余裕があればCRUD処理のテストも執筆していきたい。

同じように詰まっている人の少しでも参考になれば嬉しいです。

なお、ご指摘大歓迎です。
ご教授いただけると幸いです。

前提

本題へ入る前に僕の環境をお伝えします。

Ruby 3.1.1
Rails 6.1.5

アソシエーション
User(1) - Recipe(多)
Recipe(1) - Genre(1)

結論

  • sign_in user でしっかりとログインを行なっておく。
    • authenticate_user! を定義していると、これが働いてログイン画面に遷移させられるため。
    • なおsign_inヘルパーメソッドを使用するためにはrails_helper.rbファイルに以下を定義する必要がある。
# ===== Deviseのヘルパーを使えるようにする設定 =====
  config.include Devise::Test::IntegrationHelpers, type: :request  # typeはリクエストスペックなのか、ビュースペックなのかによって異なる
  • 従属する側のインスタンスを生成するときは、必ず親のidを指定する
spec/requests/recipes_spec.rb
describe "GET #edit" do
    subject { get(edit_recipe_path(recipe.id)) }
    let!(:user) { create(:user) }
    let!(:recipe) { create(:recipe, user_id: user.id) }
    let!(:genre) { create(:genre, recipe_id: recipe.id) }

    context "リクエストを投げたとき" do
      it "リクエストが成功する" do
        sign_in user
        subject
        expect(response).to have_http_status(:ok)
      end
    end

    context "レシピが存在するとき" do
      it "レシピのタイトル・コンテンツ・調理時間・目安費用・カロリーが表示される" do
        sign_in user
        subject
        expect(response.body).to include(
          recipe.title, recipe.content, recipe.cooking_time.to_s,
          recipe.cooking_cost.to_s, recipe.calorie.to_s
        )
      end
    end
  end

どんなエラー?

1つ1つ確認していくと、ひとつ目は
レスポンスのステータスが302だよと教えてくれている。
本来はrubocopなどでresponseの中身を確認するべき。
今回は2つ目のエラーにリダイレクト先がわかるので省略。

1) Recipes GET #edit リクエストを投げたとき リクエストが成功する
     Failure/Error: expect(response).to have_http_status(:ok)
       expected the response to have status code :ok (200) but it was :found (302)
     # ./spec/requests/recipes_spec.rb:126:in `block (4 levels) in <main>'

ふたつ目は
編集ページにリクエストを投げて帰ってきたビューにレシピの情報が表示されているかをテストしている。

2) Recipes GET #edit レシピが存在するとき レシピのタイトル・コンテンツ・調理時間・目安費用・カロリーが表示される
     Failure/Error:
       expect(response.body).to include(
         recipe.title, recipe.content, recipe.cooking_time.to_s,
         recipe.cooking_cost.to_s, recipe.calorie.to_s
       )
     
       expected "<html><body>You are being <a href=\"http://www.example.com/recipes\">redirected</a>.</body></html>" to include "形", "はなじ浸す果樹。超〜かいぞく黒板。あらすけいじばん察知。", "100", "100", and "100"
     # ./spec/requests/recipes_spec.rb:134:in `block (4 levels) in <main>'

ここで注目すべきは、以下の箇所。
リダイレクト先がrecipesつまり一覧ページにリダイレクトされていることがわかる。

expected "<html><body>You are being <a href=\"http://www.example.com/recipes\">redirected</a>.</body></html>" to include ~~~

対処法と考え方

では、どうすれば良いのか。(解決法は既に結論で述べているが...)
編集へのリクエストが失敗して一覧ページに遷移するということは、渡すパラメータの何かがおかしいということだ。

変数定義のところを見てみよう。

spec/requests/recipes_spec.rb
describe "GET #edit" do
    subject { get(edit_recipe_path(recipe.id)) }
    let!(:user) { create(:user) }
    let!(:recipe) { create(:recipe) }
    let!(:genre) { create(:genre) }

# ===== 省略 =====

end

この場合、userインスタンスが最初に生成され、次にrecipeインスタンスが生成される。そのとき、このrecipeインスタンスのuser_idはどのような値だろうか。そう、先に生成されたuserのidとは別のuserインスタンスが生成されてそれが紐づいているのだ。

つまり、編集ページへリクエストを送るとき、他人のレシピ投稿を編集しようとしているのである。もちろん、自分の投稿のみ編集できるようにコントローラ側で定義しており、そこには一覧ページへリダイレクトさせる処理もある。

まさしく、これが原因だったのだ。
よって以下のように修正すれば、テストが通るはずである。

spec/requests/recipes_spec.rb
describe "GET #edit" do
    subject { get(edit_recipe_path(recipe.id)) }
    let!(:user) { create(:user) }
    let!(:recipe) { create(:recipe, user_id: user.id) }
    let!(:genre) { create(:genre, recipe_id: recipe.id) }

    context "リクエストを投げたとき" do
      it "リクエストが成功する" do
        sign_in user
        subject
        expect(response).to have_http_status(:ok)
      end
    end

    context "レシピが存在するとき" do
      it "レシピのタイトル・コンテンツ・調理時間・目安費用・カロリーが表示される" do
        sign_in user
        subject
        expect(response.body).to include(
          recipe.title, recipe.content, recipe.cooking_time.to_s,
          recipe.cooking_cost.to_s, recipe.calorie.to_s
        )
      end
    end
  end

Discussion