resource, devise_mapping, resource_name, resource_classがないとエラーが出る。
request spec(Controllerからviewが呼ばれている)だと問題ない(Gemにある)けど、view spec(viewを直接render)では別途、用意してあげる必要があったのでメモしておきます。
RSpecで出たエラー
Failure/Error: <%= form_for(resource, as: resource_name, url: create_user_session_path, html: { method: :post, novalidate: true }, local: true) do |form| %> ActionView::Template::Error: undefined local variable or method `resource' for #<ActionView::Base:0x0000000000d7a0> ActionView::Template::Error: undefined local variable or method `resource_name' for #<ActionView::Base:0x0000000000d980>
Failure/Error: <% if devise_mapping.rememberable? %> ActionView::Template::Error: undefined local variable or method `devise_mapping' for #<ActionView::Base:0x0000000000d9a8>
Failure/Error: <% if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> ActionView::Template::Error: undefined local variable or method `resource_class' for #<ActionView::Base:0x0000000000d9d0>
view spec
spec/views/users/sessions/new.html.erb_spec.rb
require 'rails_helper' RSpec.describe 'users/sessions/new', type: :view do + before { @resource = User.new } context do it '対象の送信先と項目が含まれる' do render assert_select 'form[action=?][method=?]', create_user_session_path, 'post' do assert_select 'input[name=?]', 'user[email]' assert_select 'input[name=?]', 'user[password]' assert_select 'input[name=?]', 'user[remember_me]' assert_select 'input[name=?]', 'commit' end end it '対象のパスが含まれる' do render expect(rendered).to include("href=\"#{new_user_registration_path}\"") # アカウント登録 expect(rendered).to include("href=\"#{new_user_password_path}\"") # パスワード再設定 expect(rendered).to include("href=\"#{new_user_confirmation_path}\"") # メールアドレス確認 expect(rendered).to include("href=\"#{new_user_unlock_path}\"") # アカウントロック解除 end it '対象のパスが含まれない' do render expect(rendered).not_to include("href=\"#{new_user_session_path}\"") # ログイン end end end
@resourceをbeforeでセットして、下のhelperに渡す。
(ログインのパスはform actionと同じなので、href=も入れました)
app/helpers/devise_view_spec_helper.rb
module DeviseViewSpecHelper # Gets the actual resource stored in the instance variable def resource # instance_variable_get(:"@#{resource_name}") @resource end # Attempt to find the mapped route for devise based on request path def devise_mapping # @devise_mapping ||= request.env["devise.mapping"] Devise.mappings[resource.class.name.underscore.to_sym] end # Proxy to devise map name def resource_name devise_mapping.name end # Proxy to devise map class def resource_class devise_mapping.to end end
helperを用意して、undefined local variable or methodを回避。
普通のhelperなので、通常のリクエストやrequest specからも呼ばれそうですが、GemのDeviseController(devise-4.7.3/app/controllers/devise_controller.rb)が呼ばれるので、結果的にview specでしか使われていない。
controller_nameがセットされない
Failure/Error: expect(rendered).not_to include("href=\"#{new_user_session_path}\"") # ログイン expected "<div class=\"card card-body mb-2\" style=\"max-width: 480px\">\n <h5 class=\"card-title mb-3\">ログイン...</li>\n <li><a href=\"/users/unlock/new\">アカウントロック解除</a></li>\n </ul>\n</nav>\n</div>\n</div>\n" not to include "href=\"/users/sign_in\""
app/views/users/shared/_links.html.erb
<% if controller_name != 'sessions' %> <li><%= link_to 'ログイン', new_user_session_path %></li> <% end %>
Controller通ってない(view specな)ので、controller_nameがnilになりリンク非表示のテストが通らない。
ここではhelper使わずにテンプレに変数を渡して対応しました。(呼び出し元は明確なので)
app/views/users/sessions/new.html.erb
- <div class="card-footer pb-0 px-0"><%= render 'users/shared/links' %></div> + <div class="card-footer pb-0 px-0"><%= render 'users/shared/links', action: 'sign_in' %></div>
app/views/users/shared/_links.html.erb
- <% if controller_name != 'sessions' %> + <% if action != 'sign_in' %> <li><%= link_to 'ログイン', new_user_session_path %></li> <% end %>
actionとmethodの順番が逆
Failure/Error: assert_select 'form[action=?][method=?]', update_user_password_path(reset_password_token: params[:reset_password_token]), 'post' do assert_select 'input[name=?]', 'user[password]' assert_select 'input[name=?]', 'user[password_confirmation]' assert_select 'input[name=?]', 'commit' end Nokogiri::CSS::SyntaxError: unexpected 'post' after '[:equal, "\"/users/password\""]'
他は、form[action=?][method=?](action、methodの順)でしたが、
ここだけ、form[method=?][action=?](method、actionの順)でした。
(form_forの定義は他と同じ感じですが)
spec/views/users/passwords/edit.html.erb_spec.rb
require 'rails_helper' RSpec.describe 'users/passwords/edit', type: :view do before do @resource = User.new params[:reset_password_token] = SecureRandom.uuid end context do it '対象の送信先と項目が含まれる' do render - assert_select 'form[action=?][method=?]', update_user_password_path(reset_password_token: params[:reset_password_token]), 'post' do + assert_select 'form[method=?][action=?]', 'post', update_user_password_path(reset_password_token: params[:reset_password_token]) do assert_select 'input[name=?][value=?]', '_method', 'put' assert_select 'input[name=?]', 'user[password]' assert_select 'input[name=?]', 'user[password_confirmation]' assert_select 'input[name=?]', 'commit' end end it '対象のパスが含まれる' do render expect(rendered).to include("href=\"#{new_user_session_path}\"") # ログイン expect(rendered).to include("href=\"#{new_user_registration_path}\"") # アカウント登録 expect(rendered).to include("href=\"#{new_user_confirmation_path}\"") # メールアドレス確認 expect(rendered).to include("href=\"#{new_user_unlock_path}\"") # アカウントロック解除 end it '対象のパスが含まれない' do render expect(rendered).not_to include("href=\"#{new_user_password_path}\"") # パスワード再設定 end end end
今回のコミット内容
※他のページも同様の修正を入れています。
https://dev.azure.com/nightonly/rails-app-origin/_git/rails-app-origin/commit/44cd7d5bed2debfa449102014b2c454ac4b42d9c
序でにPUT/PATCH/DELETEをPOSTに変更して、GETとPOSTに統一しました。
(フロント実装時に気にする要素が減るので良さそう)
https://dev.azure.com/nightonly/rails-app-origin/_git/rails-app-origin/commit/5902e7c47248e223d0f890fcb92cdd472fd3a27f
フロント(Nuxt)も修正
https://dev.azure.com/nightonly/nuxt-app-origin/_git/nuxt-app-origin/commit/01a2d7d518a5f17e8c00c110fa2bdffbcb9f899f