🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Ruby on Rails 6

Ruby on Rails 6は、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

RSpec

RSpecはRuby用のBDD(behaviour-driven development)フレームワークです。

Q&A

解決済

1回答

6016閲覧

Everyday Railsでcontroller specをrequest specに書き換える方法

ryusou

総合スコア1

Ruby on Rails 6

Ruby on Rails 6は、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

RSpec

RSpecはRuby用のBDD(behaviour-driven development)フレームワークです。

0グッド

0クリップ

投稿2021/02/27 03:05

前提・実現したいこと

「Everyday Rails-RspecによるRailsテスト入門」を進めています。
第7章「リクエストスペックで API をテストする」の部分で質問です。
本書の通り、コントローラースペックをリクエストスペックで書き換える作業を行っているのですが、本書のtasksのテストをどのようにリクエストスペックで書き換えれば良いのかが分かりません。

発生している問題・エラーメッセージ

Failures: 1) Tasks #create responds with JSON formatted output Failure/Error: params.require(:project).permit(:name, :description, :due_on) ActionController::ParameterMissing: param is missing or the value is empty: project

該当のソースコード

spec/request/tasks_request_spec.rb

1require 'rails_helper' 2 3RSpec.describe "Tasks", type: :request do 4 let(:headers) do 5 { 'Accept' => 'application/json' } 6 end 7 before do 8 @user = FactoryBot.create(:user) 9 @project = FactoryBot.create(:project, owner: @user) 10 @task = @project.tasks.create!(name: "Test task") 11 end 12 13 describe "#show" do 14 #JSON形式でレスポンスを返すこと 15 it "responds with JSON formatted output" do 16 sign_in @user 17 get projects_url, headers: headers, params: { project_id: @project.id, id: @task.id } 18 expect(response.content_type).to eq "application/json" 19 end 20 end 21 describe "#create" do 22 #JSON形式でレスポンスを返すこと 23 it "responds with JSON formatted output" do 24 new_task = { name: "New test task" } 25 sign_in @user 26 post projects_url, headers: headers, params: { project_id: @project.id, task: new_task } 27 expect(response.content_type).to eq "application/json" 28 end 29 end 30end

controllerで書かれた書き換える前のテスト

RSpec.describe TasksController, type: :controller do before do @user = FactoryBot.create(:user) @project = FactoryBot.create(:project, owner: @user) @task = @project.tasks.create!(name: "Test task") end describe "#show" do # JSON 形式でレスポンスを返すこと it "responds with JSON formatted output" do sign_in @user get :show, format: :json, params: { project_id: @project.id, id: @task.id } expect(response.content_type).to eq "application/json" end describe "#create" do # JSON 形式でレスポンスを返すこと it "responds with JSON formatted output" do new_task = { name: "New test task" } sign_in @user post :create, format: :json, params: { project_id: @project.id, task: new_task } expect(response.content_type).to eq "application/json" end end

試したこと

Rspecのテストの書き方というよりかはRailsのパスの指定方法がよくわからないという問題だと思っています。
以下はサンプルアプリケーションでrake routesで出力されるPathです。

project_tasks GET /projects/:project_id/tasks(.:format) tasks#index POST /projects/:project_id/tasks(.:format) tasks#create new_project_task GET /projects/:project_id/tasks/new(.:format) tasks#new edit_project_task GET /projects/:project_id/tasks/:id/edit(.:format) tasks#edit project_task GET /projects/:project_id/tasks/:id(.:format) tasks#show

上記の通り、projectsのパス上にtaskを追加する構成になっています。
これをどのようにRspec上でparamsで設定すれば良いのかが分かりません。

以下が生成したファクトリーです

#facrories/projects.rb FactoryBot.define do factory :project do sequence(:name) { |n| "Test Project #{n}" } description { "A test project." } due_on { 1.week.from_now } association :owner #メモ付きのプロジェクト trait :with_notes do after(:create) { |project| create_list(:note, 5, project: project) } end #昨日が締め切りのプロジェクト trait :due_yesterday do due_on { 1.day.ago } end #今日が締め切りのプロジェクト trait :due_today do due_on { Date.current.in_time_zone } end #明日が締め切りのプロジェクト trait :due_tomorrow do due_on { 1.day.from_now } end #無効になっている trait :invalid do name { nil } end end end
#facrories/users.rb FactoryBot.define do factory :user, aliases: [:owner] do first_name { "Aaron" } last_name { "Aaron" } sequence(:email) { |n| "tester#{n}@example.com" } password { "dottle-nouveau-pavilion-tights-furze" } end end

補足情報(FW/ツールのバージョンなど)

rails 6.0.3.5
rspec-support 3.10.2
rspec-core 3.10.1
rspec-expectations 3.10.1
rspec-mocks 3.10.2
rspec-rails 4.0.2

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

「Everyday Rails - RSpecによるRailsテスト入門」翻訳者の伊藤です。
本書を読んでいただき、どうもありがとうございます。
ご質問の件に回答しますね。

まず、コントローラースペックではdescribeに書いたコントローラー名で、呼びだし先のコントローラーが決まります。

ruby

1RSpec.describe TasksController, type: :controller do 2 # ... 3 4 describe "#create" do 5 it "responds with JSON formatted output" do 6 new_task = { name: "New test task" } 7 sign_in user 8 9 # コントローラースペックなので、ここではTasksController#createが呼ばれる 10 post :create, format: :json, 11 params: { project_id: project.id, task: new_task } 12 expect(response.content_type).to eq "application/json; charset=utf-8" 13 end 14 15 # ... 16 end 17end

ですが、リクエストスペックでは明示的に呼び出し先のパスを指定する必要があります。
ここで、ryusouさんの書いたコードを見るとこうなっています。

ruby

1RSpec.describe "Tasks", type: :request do 2 # ... 3 describe "#create" do 4 it "responds with JSON formatted output" do 5 new_task = { name: "New test task" } 6 sign_in @user 7 8 # 呼び出しがprojects_url、つまりProjectsController#createが呼ばれる 9 post projects_url, headers: headers, params: { project_id: @project.id, task: new_task } 10 expect(response.content_type).to eq "application/json" 11 end 12 end 13end

実際、エラーメッセージに表示されている Failure/Error: params.require(:project).permit(:name, :description, :due_on) は、ProjectsController内のコードです。

https://github.com/everydayrails/everydayrails-rspec-2017/blob/63e38f87c8edfdfb5861fa628429651f62c8492e/app/controllers/projects_controller.rb#L83

ここまで来れば答えはもうお分かりかと思いますが、正しくはprojects_urlではなく、tasks_urlを指定することです。ただ、この場合は単純にprojects_urlを置き換えるだけでなく、次のように書く必要があるかもしれません。(動作未確認)

ruby

1post project_tasks_url(@project), headers: headers, params: { task: new_task }

以上、参考になれば幸いです。

コメントを受けての追記

それがproject_tasks/url(@project)にしているのですが、上記のようなエラーが出てしまいます。
該当のコードのURLは以下のようになります
https://github.com/YouheiNozaki/Everyday-rails-v6/blob/main/spec/requests/tasks_request_spec.rb

コードの共有ありがとうございます!これだと手元でもエラーを再現できるので非常に助かります。
というわけで、僕もローカルで再現させてみました。

これ、前回のコメントですっかり見落としていましたが、RSpec側のパスではなく、Viewに書かれたパスの間違いですね。
しかも、ryusouさんの間違いではなくオリジナルのサンプルコードの間違いです。(Oh...????)

https://github.com/everydayrails/everydayrails-rspec-2017/blob/90b22535d44ab626b82363335a227c5a1856a039/app/views/tasks/_task.json.jbuilder#L2

正しくはこうですね。

diff

1-json.url project_task_url([task.project, task], format: :json) 2+json.url project_task_url(task.project, task, format: :json)

こうすればテストはパスするはずです。

コントローラースペックでエラーが起きなかった理由

ではなぜViewに不具合があったのにコントローラースペックの方はパスしたのでしょうか?
これはコントローラースペックはデフォルトでViewをレンダリングしないためです。
そのため、コントローラースペックという名前の通り「コントローラーは異常なし!だからヨシ!(Viewは知らん!)」となってしまったわけです。

ただし、render_viewsというメソッドを呼びだしておくと、コントローラースペックでもViewのレンダリングを強制することができます。
こうするとコントローラースペックでもエラーが発生するようになります。

diff

1 require 'rails_helper' 2 3 RSpec.describe TasksController, type: :controller do 4+ render_views 5 include_context "project setup"

実行結果

Failures: 1) TasksController#create responds with JSON formatted output Failure/Error: json.url project_task_url([task.project, task], format: :json) ActionView::Template::Error: No route matches {:action=>"show", :controller=>"tasks", :format=>:json, :project_id=>[#<Project id: 1, name: "Project 2", description: "A test project.", due_on: "2021-03-06", created_at: "2021-02-27 07:53:57.565109000 +0000", updated_at: "2021-02-27 07:53:57.565109000 +0000", user_id: 1, completed: nil>, #<Task id: 1, name: "New test task", project_id: 1, completed: nil, created_at: "2021-02-27 07:53:57.574235000 +0000", updated_at: "2021-02-27 07:53:57.574235000 +0000">]}, missing required keys: [:id] Did you mean? project_tasks_url project_task_path project_tasks_path new_project_task_url # ./app/views/tasks/_task.json.jbuilder:2:in `_app_views_tasks__task_json_jbuilder___3745737086157069224_66100' # ./app/views/tasks/show.json.jbuilder:1:in `_app_views_tasks_show_json_jbuilder__4444021816594691676_66060' # ./app/controllers/tasks_controller.rb:34:in `block (2 levels) in create' # ./app/controllers/tasks_controller.rb:31:in `create' # ./spec/controllers/tasks_controller_spec.rb:20:in `block (3 levels) in <top (required)>' # ------------------ # --- Caused by: --- # ActionController::UrlGenerationError: # No route matches {:action=>"show", :controller=>"tasks", :format=>:json, :project_id=>[#<Project id: 1, name: "Project 2", description: "A test project.", due_on: "2021-03-06", created_at: "2021-02-27 07:53:57.565109000 +0000", updated_at: "2021-02-27 07:53:57.565109000 +0000", user_id: 1, completed: nil>, #<Task id: 1, name: "New test task", project_id: 1, completed: nil, created_at: "2021-02-27 07:53:57.574235000 +0000", updated_at: "2021-02-27 07:53:57.574235000 +0000">]}, missing required keys: [:id] # Did you mean? project_tasks_url # project_task_path # project_tasks_path # new_project_task_url # ./app/views/tasks/_task.json.jbuilder:2:in `_app_views_tasks__task_json_jbuilder___3745737086157069224_66100'

rails sで起動したサンプルアプリケーションではTaskの作成はJSONではなくHTMLリクエストで実行されることもあり、JSONレスポンスに不具合が隠れていることを検知できていませんでした。どうもすみません????‍♂️

というわけで、この件は原著者のAaronさんにフィードバックしておきました。

_task.json.jbuilder has invalid arguments for project_task_url · Issue #112 · everydayrails/everydayrails-rspec-2017

おかげさまで長年誰も気づいていなかった不具合を見つけることができました。
どうもありがとうございました!!

投稿2021/02/27 05:51

編集2021/02/27 09:04
jnchito

総合スコア357

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ryusou

2021/02/27 06:24

回答ありがとうございます!!パス指定ができるのですね。request specの仕組みと方法大変参考になりました。 回答の通りに実装して見たところ以下のようなエラーが出てしまいました。 Failures: 1) Tasks #create responds with JSON formatted output Failure/Error: post tasks_url(@project), headers: headers, params: { task: new_task } NoMethodError: undefined method `tasks_url' for #<RSpec::ExampleGroups::Tasks::Create:0x00007fcfaa498ed8>
ryusou

2021/02/27 06:26

そこで、パスをproject_tasks_url(@project)に変えて実装して見たところ、以下のようなエラーが出てしまいました。 Failures: 1) Tasks #create responds with JSON formatted output Failure/Error: json.url project_task_url([task.project, task], format: :json) ActionView::Template::Error: No route matches {:action=>"show", :controller=>"tasks", :format=>:json, :project_id=>[#<Project id: 1, name: "Test Project 25", description: "A test project.", due_on: "2021-03-06", created_at: "2021-02-27 06:25:33", updated_at: "2021-02-27 06:25:33", user_id: 1>, #<Task id: 2, name: "New test task", project_id: 1, completed: nil, created_at: "2021-02-27 06:25:33", updated_at: "2021-02-27 06:25:33">]}, missing required keys: [:id] # ./app/views/tasks/_task.json.jbuilder:2:in `_app_views_tasks__task_json_jbuilder___898240663461727768_70264941432100' # ./app/views/tasks/show.json.jbuilder:1:in `_app_views_tasks_show_json_jbuilder___3753894833669414200_70264941258680' # ./app/controllers/tasks_controller.rb:34:in `block (2 levels) in create' # ./app/controllers/tasks_controller.rb:31:in `create' # ./spec/requests/tasks_request_spec.rb:26:in `block (3 levels) in <top (required)>' # -e:1:in `<main>' # ------------------ # --- Caused by: --- # ActionController::UrlGenerationError: # No route matches {:action=>"show", :controller=>"tasks", :format=>:json, :project_id=>[#<Project id: 1, name: "Test Project 25", description: "A test project.", due_on: "2021-03-06", created_at: "2021-02-27 06:25:33", updated_at: "2021-02-27 06:25:33", user_id: 1>, #<Task id: 2, name: "New test task", project_id: 1, completed: nil, created_at: "2021-02-27 06:25:33", updated_at: "2021-02-27 06:25:33">]}, missing required keys: [:id] # ./app/views/tasks/_task.json.jbuilder:2:in `_app_views_tasks__task_json_jbuilder___898240663461727768_70264941432100'
jnchito

2021/02/27 07:28

エラーメッセージに `project_task_url([task.project, task], format: :json)` とあるので、ryusouさんが書いたコードは `project_tasks_url(@project)` にはなっていない気がします。それでも動かないようであればRSpecのコード全体をまたコピペして載せてください。
jnchito

2021/02/27 09:04

コメントありがとうございます!回答を追記しました。
ryusou

2021/02/27 09:23

回答ありがとうございました!! 手元でもテストパスできました。 また、本書の改善にもなりそうとのことでとても良かったです! request specに書き換える実装やって見て良かったです。 とても参考になりました。ありがとうございました!!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問