リクエストスペックで`GET #edit`をテストして詰まったときの話
概要
リクエストスペックの 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を指定する
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 ~~~
対処法と考え方
では、どうすれば良いのか。(解決法は既に結論で述べているが...)
編集へのリクエストが失敗して一覧ページに遷移するということは、渡すパラメータの何かがおかしいということだ。
変数定義のところを見てみよう。
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
インスタンスが生成されてそれが紐づいているのだ。
つまり、編集ページへリクエストを送るとき、他人のレシピ投稿を編集しようとしているのである。もちろん、自分の投稿のみ編集できるようにコントローラ側で定義しており、そこには一覧ページへリダイレクトさせる処理もある。
まさしく、これが原因だったのだ。
よって以下のように修正すれば、テストが通るはずである。
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