System Spec
おことわり
実装に関してはググればもっと上手く綺麗にまとめてある方の記事があるのでそちらをご覧ください。 未来の忘れた自分に対する説明の仕方なので要点だけ知りたい人には向きません。ごちゃごちゃしてます。(下手くそかよ)
必要最低限の記述の仕方は現場RailsとQiitaの記事を見れば大体わかる *雰囲気知るには'現場Rails'の方が個人的に分かりやすいかも(chapter5)
ただ、書いてないことも多いのであとは調べるしかない
-
今回の要件(いいか?ちゃんと読めよ?!*100)
タスクとユーザーに関するsystem specを作成し、下記要件のテストケースを作成してください。
[正常系] ・ユーザーの新規作成、編集ができること ・ログインが成功すること ・ログインした状態でタスクの新規作成、編集、削除ができること ・マイページにユーザーが新規作成したタスクが表示されること [異常系] ・メールアドレスが未入力時にユーザーの新規作成、編集が失敗すること ・登録済メールアドレス使用時にユーザーの新規作成、編集が失敗すること ・フォーム未入力時にログインが失敗すること ・ログインしていないユーザーでタスクの新規作成、編集、マイページへの遷移ができないこと ・他のユーザーのユーザー編集、タスク編集ページへの遷移ができないこと
共通の確認観点として「処理後の遷移先のパスがどこか」を確認してください。
[正常系] では、下記の観点も確認してください。
- 「ログイン・ユーザー新規登録の成功時に対応するフラッシュメッセージが表示されること」
- 「タスクの新規作成・編集・削除が成功した場合、対応するフラッシュメッセージと更新したデータが画面上に表示されていること」
[異常系] では、下記の観点も確認してください。
- 「ログイン・ユーザー新規登録の失敗時に対応するフラッシュメッセージが表示されること」
- 「タスクの新規作成・編集が失敗した場合、対応するフラッシュメッセージと更新しようとしたデータが画面上に表示されていないこと」
[その他の要件]
- login処理はmoduleを作成して共通化して、specファイル間で呼び出せるようにしてください。
- spec実行時にブラウザの表示有無を切り替える設定を spec/support 以下に追加してください
- 実行するテストケースを限定できる設定を spec_helper.rb の50行目辺りを確認して有効化してください。
- テストデータの作成にはFactoryBotやletを利用してください。
また作成したspecがテストとして機能しているか、ControllerやViewのコードを変更した際にテストが失敗することを確認してください
-
確認ポイント(ちゃんと読めよ??)
できる限り細かく噛み砕いてみます
そもそもSystem Specってなにだ?🌝 🍙
Railsアプリケーションの全体的なテスト。 ブラウザを通してアプリケーションの挙動を外部的に確認できる(現場Rails,190p) (192pのコラムも見たらいいかも)
(前回はModel Specだったので、その名の通りModelファイルに関するテストだった。)
「なるほ、全体テストすりゃいいんだな!」ってのが分かったが、
🤔「どこに書いたらいいんだ?」
↓
【問題文よく読め】
ちゃんと要件に書いてあるんだよ 作成 と。
さらに今回のようなSystem Specを行いたい場合は、現場Rails(5-8 / 199p)とqiitaの記事を見ると書いてあるように
ディレクトリは自身で作成する
ターミナルでコマンドで作ってもいいし、vscodeとかならマウスでポチポチ🌱作ってもいい
rspec-rails 3.7の新機能!System Specを使ってみた - Qiita
🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🍙 🐤
🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀
必要な単語
let
-
letは変数のように使えるオブジェクトを定義することができます。(現場Rails-211,212p)
ただ定義するだけでは実行されません。 コントローラーなどに作ったdef ●●のように"作ったけど使わないメソッド"と同じです。
これはletを定義した後にletまたは定義名を使わないと作っても実行されません。
- let(定義名) { 定義の内容 }
let!
-
letは使われないと実行されないですが、! をつけると常に呼び出されます。(現場Rails-214p)
「呼び出される時と呼び出されない場合があるけど、データは作っておきたい」そんな時にも使えます
使い所としてはbeforeの外に記述がある場合です。beforeが先に呼び出されてしまうと外に書いていたletは実行されなくなります。beforeの前に呼び出して欲しいのでlet!で定義します。
!を使わずにbeforeの中に書いてもいいですが、別所でまた書かないといけなくなったりするので汎用性のためです。
- let!(定義名) { 定義の内容 }
fill_in
-
fill_inはhtmlでいう<label>タグの<input>要素であるテキストフィールド(label)名を指定して、その中に値を入れてくれるメソッドです。(現場Rails-202p)
fill_in 'ほにゃらら', with: 'フガフガ' # ↑ ラベル名 ↑初めて見た
上記で言うと<input>部分の'ほにゃらら'というlabelの中に'フガフガ'という文字(値)を入れてくれます
よく見るメールフォームなら
fill_in 'Email', with: 'フガフガ'
こんな感じ。
-
あとwithってのは「ここに書いた文字入れてくれるんやな」程度だったので少しだけ調べてみました
説明にあるやつを翻訳すると
Use with to specify the expected arguments. A message expectation constrained by with will only be satisfied when called with matching arguments. A canned response for an allowed message will only be used when the arguments match. ↓ ↓ 期待される引数を指定するにはwithを使います。withで制約されたメッセージの期待値は によって制約されたメッセージの期待値は、一致する引数で呼び出された場合にのみ満たされます。許可されたメッセージに対する定型応答は 許可されたメッセージに対する定型応答は、引数が一致したときにのみ使用されます。
期待される引数を指定する
なるほどな!
click_button
-
ボタンを押してくれるメソッドです(現場Rails-203p)
Loginのボタンを押します
click_button 'Login'
送信と表示されていれば送信ボタンを押します
click_button '送信'
ちなみにclick_on と書くとボタンとリンク両方の動きをします(爆) *ただしリンク先の無いボタンやリンクは押せません
🧐 🤪 🤯 🧐 🤪 🤯 🧐 🤪 🤯 🧐 🤪 🤯 🧐 🤪 🤯 🧐 🤪 🤯 🧐 🤪 🤯
🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀 🌀
・その他の要件
- ログイン処理を作る(汎用性のUP)
-
ログインヘルパーを作る-パーフェクトRails(360p)
ログイン機能は複数のテストで共有するので、ヘルパーメソッドとしてhelperファイルに書きますログインが必要なページでもテストが行えるようにテストコードでログインする機能を実装します
イメージとしては「コントローラーじゃなくてモデルに書いとく」みたいな感じです。共通する場所に先に書いとけばあとはそのメソッドだけ使えるので、その方が汎用性上がるから。
ただ、イメージに近いけど軽く読んで今回はパス。
なぜこれをするのか?要件に書いてあるんだよ!🐻 <ダボが
一応「login module rspec」でググるとこういう記事が出てきたよ。とりあえず脳死でその通りにやってみるよ
Rspecでサインインメソッドを共通化して切り出す(devise使わないとき)[system spec][request spec] - Qiita
【Rails】はじめてのSystemSpec(RSpec) - Qiita
コメントアウトを解除したお。これ何をしてるかっていうと
"spec/support/以下が読み込めるようにパスを渡してます"
-
例えば。。
よくプロフィール写真を設定しないときにデフォルトで👤 こういった表示がされるように👤 ⇦の画像がどこかのディレクトリにあるんだけどそこから引っ張ってくる為に書かれたりするよ。「ここにある画像の場所はこのディレクトリだよ!」と言う道筋を表す書き方をパスを通すと言います。
書き方は ' ' と , で区切ってますが / でも大丈夫だ(と教えてもらった、まじ神。あざす)
- ログインするのはuserなので中にはloginする為のモジュール(module)を作成します.
-
module LoginModule # LoginModuleという名前にしたがなんでも良い def login(user) # ログインするメソッドを定義する、以下内容 visit login_path #ログイン画面にアクセスするよ click_link 'Login' # Loginリンクをクリックするよ fill_in 'Email', with: user.email # Emailラベル内にloginの引数(user)のメールを入れる(やりたい事が変わる時があるのでメアドは都度入れる必要がある) fill_in 'Password', with: 'password' # Passwordラベルにpassword って入れてくれるよ click_button 'Login' # 最後にLoginボタンを押してくれるよ end end # rails_helper.rbの下に、このLoginModuleをinclude(組み込む)してあげると、 # rails_helperをrequireしているspecファイル内でこのlogin(user)メソッドが使えるようになる
-
上で書いたモジュールが読み込まれるようにrails_helper.rbに追記します
#一番下の方 # arbitrary gems may also be filtered via: # config.filter_gems_from_backtrace("gem name") config.include FactoryBot::Syntax::Methods # spec.rb内でletを定義する中で FactoryBot. を省略してくれる config.include LoginModule # login_module.rbに記載した内容のmoduleを使えるようにする(今回はLoginModule) end
この辺分かりにくいけど、上のをやっておかないと下の"spec/support 以下に"繋がらないよね
-
思った事
これ、'support'じゃなくてもいいんだよね?
その通りみたいで自分で違うディレクトリ名でも良さそうだ。元々デフォルトで用意されていた名前が'support’ってだけの話でそのまま使ってもいいし、'hoge'とか'about'とか好きに変えても問題無いみたい。 ただちゃんとhogeディレクトリを作らないと読み込まれないから気をつけてちょんまげ
Capybara
次に行く前にCapybaraってなんだ ↓
spec実行時にブラウザの表示有無を切り替える設定を spec/support 以下に追加してください
??? 🤔 ???
このブラウザ表示の有無って辺りの言い回しがふんわりしててわからん。 なんとなくカピバラが関係してる感だけある
あ〜…
カピバラがドライバを設定してくれるので(現場Rails194p)
chome_headlessをドライバとして使ってくれるんやな(小並感)
下記記事参照
【暫定版】Rails 5.1のSystemTestCaseでHeadlessモードのChromeを使ってみる - Qiita
現場Rails(194p)にはgemの記載がないので調べてくうちに以下を発見したのでやってみましたが、「パスを通せ」っていう余計なエラーが出ます
group :test do
gem 'capybara'
… # ↓ こいつ
gem 'selenium-webdriver'
# エラー出るので以下の様に書きましょう
gem 'webdrivers'
Rspecの設定(SystemSpecの導入、実行時にブラウザ表示、非表示の切り替え設定) · Issue #5 · diveintocode-corp/rails_exam01_bugfix
-
こんがらがってるけど脳死でsupportの下にcapybara.rb作って中に記載するよ
RSpec.configure do |config| # 現場Rails194pに書いてある内容 config.before(:each, type: :system) do driven_by :selenium_chrome_headless #GUIを起動しないでブラウザテストする end end
(カピバラの動作を行う記述って意味でcapybara.rbというファイル名にしたんだと思うよ、だから別にhogehoge.rbでも良い)
headlessの意味
記事で書いてあるけどイメージつかん!ので以下
上の_headlessを記入しないで$ rspecをしちゃうとブラウザ立ち上がっちゃう。
_headless記入してるとブラウザは立ち上がらずにターミナルだけで完結する(エラーは気にしないで)
-
疑問点
現場Rails(194p)にはspec/spec_helper.rb内に require 'capybara/rspec' と書いてあるが、今回の課題にはそもそもcapybaraファイルがない。
上のログイン処理内で作成したsupport/の下にcapybara.rbというファイルを作成し中に現場railsと同じ内容を記載したが、現場Rails真似て require 'capybara/rspec'の記載をしてターミナルでrspecコマンドを打つとエラーが出る(そもそもファイル無いから当たり前)
今回のケースでは「ファイルが無いからrequire 'capybara/rspec'を読み込まなくてもいい」という判断をしたがspec_helper.rb内に記載せずに、わざわざsupport/capybara.rbに記載し、上の中でsupportを読み込むようにしたの何故だろうか。また何故capybaraファイルが無いのだろうか。と。
解決への道
- ちなみに現場Railsに書いてあった通りにやるとうまくいかなかった (triさんに助けて頂きました。ありがとうございます!)
実行するテストケースを限定できる設定を spec_helper.rb の50行目辺りを確認して有効化してください。
-
この辺のやつ
なぜか?理由は下の確認ポイント
・確認ポイントブラウザのON/OFFやテストケースのfocus実行などの設定を行っていること
特定のテストケースを実行したい時のfocus: true - その辺にいるWebエンジニアの備忘録
↑あやふやなまま、なんでこれすんの?
>> 実務で膨大なテストがあるときに全てをテストすると時間がかかるので、自分がテストしたい部分だけをしたいときにfocasを使います
expectではなるべくDBへのアクセスを行わないこと(テスト実行時の時間的コストがかかるため)
これの意味するのは「letを使え」という事。
理由としては以下の記事内にある *現場Railsならchapter5-11(212p)
RSpecのletを使うのはどんなときか?(翻訳) - Qiita
🌱 🦤...🌱 🦤...🌱 🦤...🌱 🦤...🌿🐥 ...🌱 🦤...🌱 🦤...🌱 🦤...🌱 🦤...🌱
弾かれた場所
ログアウトのテストが詰まった
describe 'ログイン後' do
context 'ログアウトボタンをクリック' do
it 'ログアウト処理が成功する' do
login(user)
click_link 'Logout' # ← click_button としていた
expect(page).to have_content 'Logged out'
expect(current_path).to eq root_path
end
end
end
end
「ボタンのタグ」か「リンクの部分なのか」というのもきちんと書き分けないと
👷👷👷👷👷👷👷👷👷👷👷👷👷🏿👷👷👷👷👷👷👷👷👷👷
Users ログイン前 ユーザー新規登録 登録済のメールアドレスを使用 ユーザーの新規作成が失敗する(3行目)
context '登録済のメールアドレスを使用' do
it 'ユーザーの新規作成が失敗する' do
ここ→ about_user = create(:user) # 違うユーザーで作成し
visit new_user_path # ユーザ新規作成ページにアクセス
ここ→ fill_in 'Email', with: about_user.email # 違うユーザのメールがmodule内の既存ユーザのメールを使う
fill_in 'Password', with: 'password'
fill_in 'Password confirmation', with: 'password'
click_button 'Update'
expect(page).to have_content '1 error prohibited this user from being saved' # 表示されるエラー文
expect(page).to have_content 'Email has already been taken' # 表示されるエラー文
expect(current_path).to eq users_path # 新規作成ページにいること
expect(page).to have_field 'Email', with: about_user.email # メールフィールド内に入力したメールアドレスが残っている
end
end
about_userという適当な変数作ってたけどこれじゃダメだった
existed_userという様にしないといけなかったのだが、existedと言いうのはRSpec内のマッチャと呼ばれる部分のメソッドであらかじめ用意されているものらしい(🍵このメソッドもたくさんある)
他の部分もマッチャやメソッドが原因だったりした🍵
context '登録済のメールアドレスを使用' do
it 'ユーザーの編集が失敗する' do
visit edit_user_path(user)
about_user = create(:user) # ← ここ
fill_in 'Email', with: about_user.email # ← ここ
fill_in 'Password', with: 'hogepassword'
fill_in 'Password confirmation', with: 'hogepassword'
click_button 'Update'
expect(page).to have_content("Email has already been taken")
expect(page).to have_content('1 error prohibited this user from being saved:')
expect(current_path).to eq user_path(user) # 失敗したらedeiできるけどidがないページであることを期待する
end
end
context '他ユーザーの編集ページにアクセス' do
it '編集ページへのアクセスが失敗する' do
about_user = create(:user) # ← ここ
visit edit_user_path(abouot_user) # ← ここ
expect(page).to have_content("Forbidden access.")
expect(current_path).to eq user_path(user)
end
end
end
ここでもabout_userとしていたがエラーが止まらない!しゅごい!
こういう場合はother_というメソッドを使うとよいので other_user に変更する
Method: RSpec::Expectations::MultipleExpectationsNotMetError#other_errors
さらにこんな感じでスクショも出してくれるので、どのあたりが間違っているかも一目瞭然
回答に書いた内容の説明(一部)
RSpec.describe 'UserSessions', type: :system do
let(:user) { FactoryBot.create(:user) } #letでuserを定義します。その内容はFactoryBotのuserをcreateします
describe 'ログイン前' do
context 'フォームの入力値が正常' do
it 'ログイン処理が成功する' do
visit login_path #ログイン画面にアクセスします
fill_in 'Email', with: user.email # letのuserのメールを渡します
fill_in 'Password', with: 'password' # letのuserのpasswordを渡します
click_button 'Login' # Loginボタンをクリックします
expect(page).to have_content 'Login successful' # 画面(page)に'Login successful'があるのを期待します
expect(current_path).to eq root_path # 今いる場所はroot(/) であるのを期待します
end
end
…
…
describe 'タスク編集' do
let!(:task) { create(:task, user: user) } # beforeより先に読み込んで欲しい
let(:other_task) { create(:task, user: user) }
before { visit edit_task_path(task) }
…
…
context '他ユーザーの編集ページにアクセス' do
it '編集ページへのアクセスが失敗する' do
other_user = create(:user) # 違うユーザーで作成し
visit edit_user_path(other_user) # 違うユーザーページにアクセスし
expect(page).to have_content("Forbidden access.") # エラーが表示され
expect(current_path).to eq user_path(user) # 失敗し自分のページであること期待する
end
end
…
…
.rspec内に
--format documentation
-
記述がない場合
すげーシンプルにテストしてくれる
-
記載がある場合
describe、context、itの部分を丁寧に表示してくれる
-
後になって気付いたこと
ヒントに書いてあったよ…
テスト実行時のブラウザについて
rails generate rspec:system
で作成されるファイルではdriven_by
で実行時のブラウザを指定しています。この記載を削除し、他のファイルでブラウザの指定を一元管理してしまいましょう。
今なら上の意味が少し理解できる。ナンだったんだ…🍛
自分で書くのは辛いです
終