username:~/workspace (master) $ rails routes | grep taggings | grep new new_book_tagging GET /books/:book_id/
taggings/new(.:format) taggings#new
taggingsは、booksに依存して呼ぶように変更しましたので、テストでも同じ修正が必 要です。テストでは、この部分でアクションを呼んでいます。
get :new, params: {}, session: valid_session
これを次のように変更しましょう。
get :new, params: { book_id: book.id }, session: valid_session
params:
の後ろのハッシュに、URLで必要なパラメータを与えることができます。すべ ての tagging は books に依存していますから、editcreateupdatedestroyも
すべてbook_idを与えるように修正してください。そうしてテストを実行すると、また別のエラーが出てきました。
username:~/workspace (master) $ rspec spec/controllers/taggings_controller_spec.rb ..FFFFFFFF
Failures:
1) TaggingsController POST #create with valid params creates a new Tagging Failure/Error: format.html { redirect_to @tagging,
notice: 'Tagging was successfully created.' } NoMethodError:
undefined method tagging_url' for #<TaggingsController:0x00000003fb7428>
Did you mean? tag_url
# /usr/local/rvm/gems/ruby-2.3.0/gems/turbolinks-5.0.1/lib/turbolinks/
redirection.rb:12:in redirect_to'
# ./app/controllers/taggings_controller.rb:32:in block (2 levels) in create' # ./app/controllers/taggings_controller.rb:30:in create'
# /usr/local/rvm/gems/ruby-2.3.0/gems/rails-controller-testing-1.0.1/lib/
rails/controller/testing/template_assertions.rb:61:in process' # /usr/local/rvm/gems/ruby-2.3.0/gems/rails-controller-testing-1.0.1/lib/
rails/controller/testing/integration.rb:12:in block (2 levels) in <module:Integration>'
# ./spec/controllers/taggings_controller_spec.rb:57:in block (5 levels) in
<top (required)>'
# ./spec/controllers/taggings_controller_spec.rb:56:in block (4 levels) in
<top (required)>' (以下省略)
undefined method 'tagging_url'
というエラーが出てきました。前に設定したapp/controllers/taggings_controller.rb
のredirect_to
を思い出して ください。format.html { redirect_to @book, notice:
'Tagging was successfully created.' }
create
update
destroy
のあとはbooks#show
にリダイレクトしますから、テストコー ドのリダイレクトを以下のように変更しましょう。expect(response).to redirect_to(book_path(book))
これでテストを実行して、全て通ることを確認できたらOKです。
• taggingsコントローラのリファクタリング(DRY原則とテストの恩恵)
app/controllers/taggings_controller.rb
を 見 てくだ さ い。@book = Book.find(params[:book_id])
でのが繰り返し出てくるのがわかると思います。こ れはDRY原則(Don'tRepeatYourself)に反しますから、リファクタリングをしましょう。リファクタリングをするときには、テストがあることが望ましいです。そのほうが同じ機能を 満たしていることが保証されますし、他のコードを壊してしまう心配もありません。ここでは、
同じ機能を持つところを抜き出してメソッドにし、必要であれば抽象化をして、該当するとこ ろでそのメソッドを呼び出す、という流れになります。では、まず抜き出してメソッドを作り ましょう。実は、コントローラは同じようなメソッドがすでにあります。
def set_tagging
@tagging = Tagging.find(params[:id]) end
これと同じように、@bookを作成してくれるメソッドを作ります。
def set_book
@book = Book.find(params[:book_id]) end
これを該当するところで呼び出せばいいのですが、set_taggingと同じように呼び出し たほうが一貫性があって後からわかりやすいと思います。呼び出すところは、コントローラ ファイルの一番最初の部分です。
before_action :set_tagging, only: [:edit, :update, :destroy]
同じように before_action で呼び出しましょう。
@book は全てのアクションで使用し
ますから、only:
やexcept: は必要ありません。before_action :set_book
そして、各アクションの@book の行を忘れずに削除してください。これでテストを実行して、
エラーがなければうまくリファクタリングできています。少しコードがすっきりしてかっこよく なりましたね。
• ルーティングテスト
次はルーティングテストです。まずはテストを実行してみましょう。
username:~/workspace (master) $ rspec spec/routing/
...FFFFFFFF..F...
Failures:
1) TaggingsController routing routes to #index
Failure/Error: expect(:get => "/taggings").to route_to("taggings#index") No route matches "/taggings"
# ./spec/routing/taggings_routing_spec.rb:7:in block (3 levels) in <top (required)>' 2) TaggingsController routing routes to #new
Failure/Error: expect(:get => "/taggings/new").to route_to("taggings#new") No route matches "/taggings/new"
# ./spec/routing/taggings_routing_spec.rb:11:in block (3 levels) in <top (required)>' 3) TaggingsController routing routes to #show
Failure/Error: expect(:get => "/taggings/1").to route_to("taggings#show",
:id => "1")
No route matches "/taggings/1"
# ./spec/routing/taggings_routing_spec.rb:15:in block (3 levels) in <top (required)>' 4) TaggingsController routing routes to #edit
Failure/Error: expect(:get => "/taggings/1/edit").to route_to("taggings#edit",
:id => "1")
No route matches "/taggings/1/edit"
# ./spec/routing/taggings_routing_spec.rb:19:in block (3 levels) in <top (required)>' 5) TaggingsController routing routes to #create
Failure/Error: expect(:post => "/taggings").to route_to("taggings#create") No route matches "/taggings"
# ./spec/routing/taggings_routing_spec.rb:23:in block (3 levels) in <top (required)>' 6) TaggingsController routing routes to #update via PUT
Failure/Error: expect(:put => "/taggings/1").to route_to("taggings#update",
:id => "1")
No route matches "/taggings/1"
# ./spec/routing/taggings_routing_spec.rb:27:in block (3 levels) in <top (required)>' 7) TaggingsController routing routes to #update via PATCH
Failure/Error: expect(:patch => "/taggings/1").to route_to("taggings#update",
:id => "1")
No route matches "/taggings/1"
8) TaggingsController routing routes to #destroy
Failure/Error: expect(:delete => "/taggings/1").to route_to("taggings#destroy",
:id => "1")
No route matches "/taggings/1"
# ./spec/routing/taggings_routing_spec.rb:35:in block (3 levels) in <top (required)>' 9) TagsController routing routes to #show
Failure/Error: expect(:get => "/tags/1").to route_to("tags#show", :id => "1") No route matches "/tags/1"
# ./spec/routing/tags_routing_spec.rb:15:in block (3 levels) in <top (required)>' Finished in 0.04067 seconds (files took 1.87 seconds to load)
24 examples, 9 failures Failed examples:
rspec ./spec/routing/taggings_routing_spec.rb:6 # TaggingsController routing routes to #index
rspec ./spec/routing/taggings_routing_spec.rb:10 # TaggingsController routing routes to #new
rspec ./spec/routing/taggings_routing_spec.rb:14 # TaggingsController routing routes to #show
rspec ./spec/routing/taggings_routing_spec.rb:18 # TaggingsController routing routes to #edit
rspec ./spec/routing/taggings_routing_spec.rb:22 # TaggingsController routing routes to #create
rspec ./spec/routing/taggings_routing_spec.rb:26 # TaggingsController routing routes to #update via PUT
rspec ./spec/routing/taggings_routing_spec.rb:30 # TaggingsController routing routes to #update via PATCH
rspec ./spec/routing/taggings_routing_spec.rb:34 # TaggingsController routing routes to #destroy
rspec ./spec/routing/tags_routing_spec.rb:14 # TagsController routing routes to #show
コントローラテストのところで見たようなエラーもありますね。
No route matches "/taggings/1"
これ は もう説 明 するまで も な い でしょう。例 えば、spec/routing/taggings_
routing_spec.rb
の中で、expect(:get => "/taggings/new").to route_to("taggings#new")
このように /taggings となっているところをすべて /books/:book_id/taggings に置き換えてください。このとき、
:book_id
にあたる部分の数字は、テスト用データの 値であれば何でもかまいません。まずは、book とそれに紐づく tagging を呼び出し
ます。let(:book) { Book.first }
let(:tagging) { book.taggings.first }
これらをURLのidとして渡すのですが、文字列の中にRubyを書くときは、ダブルクォーテー ション+シャープ+波カッコで囲むのでした。ですので、このテスト全体は以下の通りにな ります。indexアクションとshowアクションはありませんから、忘れずに削除してください。
require "rails_helper"
RSpec.describe TaggingsController, type: :routing do describe "routing" do
let(:book) { Book.first }
let(:tagging) { book.taggings.first } it "routes to #new" do
expect(:get => "/books/#{book.id}/taggings/new").to route_to("taggings#new", :book_id => "#{book.id}") end
it "routes to #edit" do
expect(:get => "/books/#{book.id}/taggings/#{tagging.id}/edit").to route_to("taggings#edit", :id => "#{tagging.id}",
:book_id => "#{book.id}") end
it "routes to #create" do
expect(:post => "/books/#{book.id}/taggings").to
route_to("taggings#create", :book_id => "#{book.id}") end
it "routes to #update via PUT" do
expect(:put => "/books/#{book.id}/taggings/#{tagging.id}").to route_to("taggings#update", :id => "#{tagging.id}", :book_id => "#{book.id}")
end
it "routes to #update via PATCH" do
expect(:patch => "/books/#{book.id}/taggings/#{tagging.id}").to route_
to("taggings#update", :id => "#{tagging.id}", :book_id => "#{book.id}") end
it "routes to #destroy" do
expect(:delete => "/books/#{book.id}/taggings/#{tagging.id}").to route_
to("taggings#destroy", :id => "#{tagging.id}", :book_id => "#{book.id}") end
end end
ルーティングのもう一つのエラーは、tags#showに対するエラーです。このアクションも 削除しましたので、spec/routing/tags_routing_spec.rbから削除してしまって ください。
これでルーティングのテストはすべてOKのはずです。
commit
• ビューテスト
ビューのテストは、後で説明するリクエストテストである程度代用できることや、修正の頻度 の高さから、Rubyでここまで書くプロジェクトは少ないようです。また、今回の1stリリー スでは扱いませんが、javascriptを多用するような場合においては、このテストではなく、
gemなどを用いて別のファイルを用意したりします。ここでは、簡単に修正ポイントだけご 紹介しておきます。
まず、何度も出てきますが、コントローラで削除したアクションに対応するビューはファイル ごと削除してください。
削除対象
-- spec/views/taggings/show.html.erb_spec.rb -- spec/views/taggings/
index.html.erb_spec.rb -- spec/views/taggings/show.html.erb_spec.rb
このとき、app/views 以下に該当のファイルが残っている場合も削除しましょう。
また、spec/views/books/index.html.erb_spec.rb の中で、一覧表の表示内 容を確認している部分があります。
assert_select "tr>td", :text => 2.to_s, :count => 2
いま、
Bookの一覧ページには価格を表示していませんので、この確認は削除しましょう。
それから、taggings#new と