💡
この記事は Middleman 時代に書いた古いものです。記録のため、astro-notion-blog に移行していますが、あまり参考にしないでください。
request spec の設置
Rails 5 からは controller_spec を描くことは非推奨となり,request_spec で記述することが推奨されている(参照: Rails5でコントローラのテストをController specからRequest specに移行する ). request_spec では,controller_spec が行なっていたようなコントローラの内部実装に関わる点はテストから除外し,リクエスト/レスポンスにのみ関心を持つブラックボックステストを行う.
kurasus_controller と integration_test の作成
-
最初に kurasus_controller を作成する(2行目以降は結果).
$ bin/rails g controller kurasus Running via Spring preloader in process 63130 create app/controllers/kurasus_controller.rb invoke haml create app/views/kurasus invoke rspec invoke assets invoke js invoke css
-
routes は作ってくれないので,config/routes.rb に resources を追加する.
# config/routes.rb Rails.application.routes.draw do devise_for :teachers get 'pages/home' root to: "pages#home" resources :kurasus end
-
integration_test は同時に作成してくれないので,別途作成する(2行目以降は結果).
$ bin/rails g integration_test kurasus Running via Spring preloader in process 70314 invoke rspec create spec/requests/kurasus_spec.rb
-
app/controllers/kurasus_controller.rb を保存した時に,spec/requests/kurasus_spec.rb をテストして欲しいので,Guardfile に設定を追加する.
watch(rails.controllers) do |m| [ rspec.spec.call("routing/#{m[1]}_routing"), rspec.spec.call("controllers/#{m[1]}_controller"), rspec.spec.call("acceptance/#{m[1]}"), rspec.spec.call("requests/#{m[1]}") # これを追加 ] end
テスト環境の準備
-
integration_test では spec/requests/kurasus_spec.rb が作成された. 現在はデフォルトのひな形で作成されているが,これから作成する kurasus_spec.rb をベースに後でひな形を新規に作成することにする. このひな形では,index でレスポンス 200 が返ってくることを期待している.
require 'rails_helper' RSpec.describe "Kurasus", type: :request do describe 'GET #index' do it "works! (now write some real specs)" do get kurasus_path expect(response).to have_http_status(200) end end end
-
今回の実装では,ログインしていない場合にクラス一覧は表示できない. このため,ログインしている場合としていない場合で対応が異なる必要があり,それもテストする必要がある. integration_test でログインを実現するために,teacher 権限でログイン/ログアウトをするためのヘルパメソッドを作成しておく. teacher_login 自体は内部で RSpec の before と after を設置するだけである. before では login_teacher_as,after では sign_out_one を呼び出している. 特に sign_out_one の呼び出しは忘れがちなので,ヘルパで一括設定するようにしてしまっている.
# @param [Symbol] fg_key user の FactoryBot のキー # @return [User] ログインしたユーザ def login_teacher_as(fg_key) @one = user_factory(fg_key) sign_in @one allow(controller).to receive(:current_teacher).and_return(@one) @one end # @note サインアウト def sign_out_one sign_out @one end # @param [Symbol] fg_key user の FactoryBot のキー # @note before と after を自動設置する. def teacher_login(fg_key) before { login_teacher_as fg_key } after { sign_out_one } end
/kurasus のテスト
ひとまず /kurasus のテストを書いてみる
-
spec/requests/kurasus_spec.rb を以下のように変更する.いつものようにソースには書いていないが,説明のためコメントを追加している.
require 'rails_helper' RSpec.describe :Kurasus, type: :request do # devise の integration test のためのヘルパを追加 include Devise::Test::IntegrationHelpers # create, update, destroy 後にリダイレクトするパスを設定しておく(遅延実行なので必要となった時だけ作成される). let(:return_path) { kurasus_path(edit_mode: true) } # hkob でログインしている時の context context 'login by hkob' do # 事前に 2300 クラスを作っておく let!(:object) { kurasu_factory :hkob2300 } # 事前に 5300(obsolete) クラスを作っておく let!(:others) { kurasu_factory :hkob5300o } # テスト開始時に hkob でログイン,終了後ログアウトを設定 teacher_login :hkob describe 'GET #index' do # 通常時のコンテキスト (obsolete は表示しない) context 'normal mode' do # オプションなしで index を描画 subject { -> { get kurasus_path } } # リクエストは正常 it_behaves_like :response_status_check, 200 # 2300 クラスは表示 it_behaves_like :response_body_includes, '2300' # 5300 クラスは非表示 it_behaves_like :response_body_not_includes, '5300' end # obsolete 表示時のコンテキスト (全クラスを表示) # obsolete オプションありで index を描画 context 'edit mode' do subject { -> { get kurasus_path(edit_mode: true) } } # リクエストは正常 it_behaves_like :response_status_check, 200 # 2300, 5300 クラスは共に表示 it_behaves_like :response_body_includes, %w[2300 5300] end end end # ログインしていない時の context context 'not login' do describe 'GET #index' do subject { -> { get kurasus_path } } # レスポンスはリダイレクト it_behaves_like :response_status_check, 302 # 2300, 5300 クラスは共に非表示 it_behaves_like :response_body_not_includes, %w[2300 5300] end end end
-
保存すると guard の画面に大量にエラーが出力される.エラーの原因は全て同じで以下に示した ActionNotFound である.
AbstractController::ActionNotFound: The action 'index' could not be found for KurasusController
-
ひとまずテストが動く環境を作ることにする.app/controllers/kurasus_controller.rb に index のメソッドがないので,とりあえず空で作成する
class KurasusController < ApplicationController def index end end
-
また,app/views/kurasus/index.html.haml のファイルを空で作成しておく
touch app/views/kurasus/index.html.haml
-
この結果,guard の出力は以下のようになった(もし変わっていない場合には,guard のコンソールでリターンキーを一度タイプするとよい).
Kurasus login by hkob GET #index normal mode behaves like response_status_check response should be 200 behaves like response_body_includes response body includes ["クラス一覧:", "小林弘幸", "2300"] (FAILED - 1) behaves like response_body_not_includes response body does not include 5300 edit mode behaves like response_status_check response should be 200 behaves like response_body_includes response body includes ["クラス一覧:", "小林弘幸", "2300", "5300"] (FAILED - 2) not login GET #index behaves like response_status_check response should be 302 (FAILED - 3) behaves like response_body_not_includes response body does not include ["クラス一覧:", "小林弘幸", "2300", "5300"] (中略) Finished in 2.67 seconds (files took 0.21094 seconds to load) 7 examples, 3 failures
-
まず一番最後にあるログインしていない時にリダイレクトするテストを通すことにする. これは pages#home と同じで before_action を追加するだけである. この追加によりこのテストは通過した.
class KurasusController < ApplicationController before_action :authenticate_teacher!
-
次に edit_mode の時のクラス一覧表示を実装してみる. edit_mode 時には obsolete のクラスも全部表示することとする. まず,app/controllers/kurasus_controller.rb に教員が所有するクラス一覧を取得するコードを追加する.
def index @kurasus = current_teacher.kurasus.order_name end
-
取得したクラスを表示する view を app/views/kurasus/index.html.haml に追加する. edit_mode の時には編集関係のリンクが発生するようにしている. また,content_for では @title を設定している.これは,app/views/layouts/application.html.haml から呼び出される想定である. t_ars は ActiveRecord の locale を取得するメソッドである.locale から取得することで,属性名のゆらぎを回避できる. さらに,new, show, edit については,各 view のタイトルをそのままリンク名にしている. ただし,destroy と confirm メッセージについては,ページがあるわけではないので,index 内のメッセージを利用する.
- content_for :title do - @title = "#{t '.title'}: #{current_teacher.name}" %table - if @edit_mode %caption= link_to t('kurasus.new.title'), new_kurasu_path %tr - t_ars(Kurasu, %i[name]).each do |w| %th= w %th= t('control') - @kurasus.each do |k| %tr %td= k.name %td = link_to t('kurasus.show.title'), kurasu_path(k) - if @edit_mode = link_to t('kurasus.edit.title'), edit_kurasu_path(k) = link_to t('.destroy'), kurasu_path(k), method: :delete, data: {confirm: t('.confirm')}
-
まず content_for で設定したタイトルをブラウザのタイトルとしたい. そこで,app/views/layouts/application.html.haml の title にて,haml の content_for を呼び出すように変更する.
%title= content_for?(:title) ? yield(:title) : :Attendance
-
さらにページタイトルとして yield の前に h1 でタイトルを表示するように変更する.
%h1= @title = yield
-
jp.kurasus.index.title などの locale を config/locales/views.ja.yml に追加する.
ja: control: 制御 kurasus: index: title: クラス一覧 enter_edit_mode: 編集モードに入る destroy: クラス削除 confirm: クラスを削除してよろしいですか? show: title: クラス表示 new: title: クラス作成 edit: title: クラス編集
-
t_ars メソッドは app/helpers/applitaion_helper.rb に追記する.
# @param [Object] model モデルクラス # @param [Array<String>] atr 属性名 def t_ar(model, atr = nil) if atr return model.human_attribute_name(atr) else return model.model_name.human end end # @param [Object] model モデルクラス # @param [Array<String>] keys 属性名の配列 # @return [Array<String>] 翻訳後の文字列の配列 def t_ars(model, keys) keys.map { |key| model.human_attribute_name(key) } end
-
jp.activerecord.attributes.kurasu.name の locale を config/locales/models.ja.yml に追加する.
ja: activerecord: models: kurasu: クラス attributes: kurasu: name: クラス名 obsolete: 未使用
-
ここまでの修正の結果,edit_mode における obsolete の絞り込み以外はテスト通過した.
Kurasus login by hkob GET #index normal mode behaves like response_status_check response should be 200 behaves like response_body_includes response body includes ["クラス一覧:", "小林弘幸", "2300"] behaves like response_body_not_includes response body does not include 5300 (FAILED - 1) obsolete view mode behaves like response_status_check response should be 200 behaves like response_body_includes response body includes ["クラス一覧:", "小林弘幸", "2300", "5300"] not login GET #index behaves like response_status_check response should be 302 behaves like response_body_not_includes response body does not include ["クラス一覧:", "小林弘幸", "2300", "5300"] (中略) Finished in 0.63085 seconds (files took 0.79338 seconds to load) 7 examples, 1 failure
-
このテストを通過するように app/controllers/kurasus_controller.rb において, edit_mode 時のみに not_obsolete を追加する.
class KurasusController < ApplicationController before_action :authenticate_teacher! def index @edit_mode = true if params[:edit_mode] @kurasus = current_teacher.kurasus.order_name @kurasus = @kurasus.not_obsolete unless @edit_mode end end
-
これによって全てのテストは通過した.
Kurasus login by hkob GET #index normal mode behaves like response_status_check response should be 200 behaves like response_body_includes response body includes ["クラス一覧:", "小林弘幸", "2300"] behaves like response_body_not_includes response body does not include 5300 obsolete view mode behaves like response_status_check response should be 200 behaves like response_body_includes response body includes ["クラス一覧:", "小林弘幸", "2300", "5300"] not login GET #index behaves like response_status_check response should be 302 behaves like response_body_not_includes response body does not include ["クラス一覧:", "小林弘幸", "2300", "5300"] Finished in 0.19479 seconds (files took 0.21901 seconds to load) 7 examples, 0 failures
長くなったので今日はここまで