RSpecのシステムテスト
Gemfileに以下のように記載する
[Gemfile] group :test do gem "capybara" gem 'webdrivers' end
※
・[capybara]Gem: ブラウザ上でのリンクのクリック、フォームの入力等のUI操作コマンドで実行できるようにするもの。
・[webdrivers]Gem: ChromeDriverを簡単に導入してくれるGem(プログラミングを介して、ブラウザを操作するライブラリ)
・ChromeDriver: Google Chromeを操作するために必要なドライバ(ソフト)
・WebDriver: ブラウザの操作自体をブラウザの拡張機能やOSの機能を利用して、実行できるようなツール
・Selenium: ブラウザ操作を自動化するツール
・headlessブラウザ: 画面に表示されないブラウザを裏で実行する
↓
$ bundle install
↓
[.rspec]ファイルに以下のように記述
[.rspec ファイル]
--require spec_helper
--format documentation
[--format documentation]を加えることでテスト実行時の結果がメッセージ付きで見やすくなる
↓
[spec/support]以下のファイルを読み込ませたり、作成したmoduleを読み込ませるために以下のようにコメントアウトを外したり記述を追加する
[spec/rails_helper.rb] Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f } # [spec/support]以下の全てのファイルが自動的に読み込まれる① RSpec.configure do |config| config.include LoginModule # 自分で作成したmoduleを読み込む(こちらはファイルの1番下に追加する ) end
spec配下にある[spec.rb]で終わるファイルは[bundle exec rspec]を実行すると自動的に読み込まれるので、このファイルを[spec/support]以下に置くと読み込み時とテスト時で2回実行されてしまうので、[spec/support]以下に[spec.rb]で終わるファイルは、置かない。①の記述をすることで、[spec/support]以下の全てのファイルが自動的に読み込まれる
↓
下記は、SystemSpecを実行するドライバの設定をしている
ドライバとは、Capybaraを使ったテストにおいてブラウザの代わりになるもの(今回は、処理が高速なHeadlessChormeを利用)
[spec/support/capybara.rb] RSpec.configure do |config| config.before(:each, type: :system) do # System Specを使用するための設定 driven_by :selenium, using: :headless_chrome # Spec実行時にブラウザを起動せずにテストを実施できる(Specテストでブラウザに相当するプログラムを指定している。ここでは、ブラウザを画面に表示しないHeadlessブラウザの1種headless_chromeを使っている。) end end
↓
[spec/spec_helper.rb]を以下のように記述(=beginの位置を変える)する事で、[config.filter_run_when_matching :focus]をオンにすることができる。
[config.filter_run_when_matching :focus]をオンにすると、限定的にテストを実行することができる
[spec/spec_helper.rb] # The settings below are suggested to provide a good initial experience # with RSpec, but feel free to customize to your heart's content. # This allows you to limit a spec run to individual examples or groups # you care about by tagging them with `:focus` metadata. When nothing # is tagged with `:focus`, all examples get run. RSpec also provides # aliases for `it`, `describe`, and `context` that include `:focus` # metadata: `fit`, `fdescribe` and `fcontext`, respectively. config.filter_run_when_matching :focus =begin
↓
以下のように[focus: true]オプションを付与するとテストを限定的に実行できるので、実行したいテストのみを実行でき無駄な時間がかからない
describe 'ログイン前' do describe 'ページ遷移確認' do context 'タスクの新規登録ページへアクセス' do it 'タスクの新規登録ページへのアクセスに失敗する' do visit new_task_path expect(page).to have_content "Login required" expect(current_path).to eq login_path end end context 'タスクの編集ページへのアクセス', focus: true do # ここの部分 it 'タスクの編集ページへのアクセスに失敗する' do visit edit_task_path(task) expect(page).to have_content "Login required" expect(current_path).to eq login_path end end
↓
rspec実行時にログイン処理が必要になる項目があるので、moduleでログイン処理をまとめておく(このmoduleは、[spec/rails_helper.rb]で読み込んでおり、各rspecテストは、require記述により、[spec/rails_helper.rb]を読み込んでいるので使える)
[spec/support/login_module.rb] module LoginModule def login_as(user) visit login_path fill_in "email", with: user.email fill_in "password", with: "password" click_button "Login" end end
上記記述で、[fill_in "password", with: user.crypted_password]のように記述しても[sorcery]Gemによってパスワードがハッシュ化されており、ハッシュ化されたパスワードからは、元々のパスワードが割り出せないため、[FactoryBot]Gemでパスワードは、[password]で固定(userが何度生成されても同じ)されているため、直接[password]をfill inに入力している
↓
下記がRSpecのsystemテストになる
[spec/system/tasks_spec.rb] require 'rails_helper' RSpec.describe "Tasks", type: :system do let(:task) { create(:task) } let(:user) { create(:user) } describe 'ログイン前' do describe 'ページ遷移確認' do context 'タスクの新規登録ページへアクセス' do it 'タスクの新規登録ページへのアクセスに失敗する' do visit new_task_path expect(page).to have_content "Login required" expect(current_path).to eq login_path end end context 'タスクの編集ページへのアクセス' do it 'タスクの編集ページへのアクセスに失敗する' do visit edit_task_path(task) expect(page).to have_content "Login required" expect(current_path).to eq login_path end end context 'タスクの詳細ページへのアクセス' do it 'タスクの詳細ページへアクセスされる' do visit task_path(task) expect(page).to have_content task.title expect(page).to have_content task.content expect(page).to have_content task.status expect(current_path).to eq task_path(task) end end context 'タスクの一覧ページへのアクセス' do it 'タスクの一覧ページへアクセスされる' do task_list = create_list(:task, 3) visit tasks_path expect(page).to have_content task_list[0].title expect(page).to have_content task_list[0].content expect(page).to have_content task_list[0].status expect(page).to have_content task_list[1].title expect(page).to have_content task_list[1].content expect(page).to have_content task_list[1].status expect(page).to have_content task_list[2].title expect(page).to have_content task_list[2].content expect(page).to have_content task_list[2].status expect(current_path).to eq tasks_path end end end end describe 'ログイン後' do before { login_as(user) } describe 'タスクの新規登録' do context 'フォームの入力値が正常' do it 'タスクの新規登録が成功する' do visit root_path click_link "New task" fill_in "task_title", with: "test" fill_in "task_content", with: "test_content" find("#task_status").find("option[value = 'todo']").select_option ① fill_in "task_deadline", with: DateTime.new(2021, 8, 1, 10, 30) click_button "Create Task" expect(page).to have_content "Task was successfully created." expect(page).to have_content "test" expect(page).to have_content "test_content" expect(page).to have_content "todo" expect(current_path).to eq '/tasks/1' ② end end context 'タイトルが未入力' do it 'タスクの新規登録が失敗する' do visit root_path click_link "New task" fill_in "task_title", with: "" fill_in "task_content", with: "test_content" find("#task_status").find("option[value='todo']").select_option fill_in "task_deadline", with: DateTime.new(2021, 8, 1, 10, 30) click_button "Create Task" expect(page).to have_content "1 error prohibited this task from being saved:" expect(page).to have_content "Title can't be blank" expect(current_path).to eq tasks_path end end context '登録済みのタイトルを入力' do it 'タスクの新規登録が失敗する' do task = create(:task) visit root_path click_link "New task" fill_in "task_title", with: task.title fill_in "task_content", with: "test_content" find("#task_status").find("option[value='todo']").select_option fill_in "task_deadline", with: DateTime.new(2021, 8, 1, 10, 30) click_button "Create Task" expect(page).to have_content "1 error prohibited this task from being saved:" expect(page).to have_content "Title has already been taken" expect(current_path).to eq tasks_path end end end describe 'タスクの編集' do let!(:task) { create(:task, user: user) } let(:other_task) { create(:task, user: user) } context 'フォームの入力値が正常' do it 'タスクの編集に成功する' do visit root_path click_link "Edit" fill_in "task_title", with: "update_title" fill_in "task_content", with: "update_content" find("#task_status").find("option[value='doing']").select_option click_button "Update Task" expect(page).to have_content "Task was successfully updated." expect(page).to have_content "update_title" expect(page).to have_content "update_content" expect(page).to have_content "doing" expect(current_path).to eq task_path(task) end end context 'タイトルが未入力' do it 'タスクの編集に失敗する' do visit root_path click_link "Edit" fill_in "task_title", with: "" fill_in "task_content", with: task.content find("#task_status").find("option[value='todo']").select_option click_button "Update Task" expect(page).to have_content "1 error prohibited this task from being saved:" expect(page).to have_content "Title can't be blank" expect(current_path).to eq task_path(task) end end context '登録済みのタイトルを入力' do it 'タスクの編集に失敗する' do visit root_path click_link "Edit" fill_in "task_title", with: other_task.title fill_in "task_content", with: "test_content" find("#task_status").find("option[value='todo']").select_option click_button "Update Task" expect(page).to have_content "1 error prohibited this task from being saved:" expect(page).to have_content "Title has already been taken" expect(current_path).to eq task_path(task) end end end describe 'タスクの削除' do let!(:task) { create(:task, user: user) } it 'タスクの削除に成功する' do visit root_path click_link "Destroy" expect(page.accept_confirm).to have_content "Are you sure?" expect(page).to have_content "Task was successfully destroyed." expect(current_path).to eq tasks_path expect(page).not_to have_content task.title end end end end
[spec/system/user_sessions_spec.rb] require 'rails_helper' RSpec.describe "UserSessions", type: :system do let(:user) { create(:user) } describe 'ログイン前' do context 'フォームの入力値が正常' do it 'ログイン処理が成功する' do visit login_path fill_in "email", with: user.email fill_in "password", with: "password" click_button "Login" expect(page).to have_content "Login successful" expect(current_path).to eq root_path end end context 'フォームが未入力' do it 'ログイン処理が失敗する' do visit login_path fill_in "email", with: "" fill_in "password", with: "" click_button "Login" expect(page).to have_content "Login failed" expect(current_path).to eq login_path end end end despecscribe 'ログイン後' do context 'ログアウトボタンをクリック' do it 'ログアウト処理が成功する' do login_as(user) visit root_path click_link "Logout" expect(page).to have_content "Logged out" expect(current_path).to eq root_path end end end end
[spec/system/users_spec.rb] require 'rails_helper' RSpec.describe "Users", type: :system do let(:user) { create(:user)} describe 'ログイン前' do describe 'ユーザー新規登録' do context 'フォームの入力値が正常' do it 'ユーザーの新規作成が成功する' do visit root_path click_link "SignUp" fill_in "user_email", with: 'sample@sample.com' fill_in "user_password", with: 'password' fill_in "user_password_confirmation", with: 'password' click_button "SignUp" expect(page).to have_content "User was successfully created." expect(current_path).to eq login_path end end context 'メールアドレスが未入力' do it 'ユーザーの新規作成が失敗する' do visit root_path click_link "SignUp" fill_in "user_email", with: "" fill_in "user_password", with: 'password' fill_in "user_password_confirmation", with: 'password' click_button "SignUp" expect(page).to have_content "1 error prohibited this user from being saved:" expect(page).to have_content "Email can't be blank" expect(current_path).to eq users_path end end context '登録済のメールアドレスを使用' do it 'ユーザーの新規作成が失敗する' do other_user = create(:user) visit root_path click_link "SignUp" fill_in "user_email", with: other_user.email fill_in "user_password", with: "password" fill_in "user_password_confirmation", with: "password" click_button "SignUp" expect(page).to have_content "1 error prohibited this user from being saved:" expect(page).to have_content "Email has already been taken" expect(page).to have_field "user_email", with: other_user.email expect(current_path).to eq users_path end end end describe 'マイページ' do context 'ログインしていない状態' do it 'マイページへのアクセスが失敗する' do visit user_path(user) expect(page).to have_content "Login required" expect(current_path).to eq login_path end end end end describe 'ログイン後' do before { login_as(user) } describe 'ユーザー編集' do context 'フォームの入力値が正常' do it 'ユーザーの編集が成功する' do visit users_path click_link "Edit" fill_in "user_email", with: "test@sample.com" fill_in "user_password", with: "test" fill_in "user_password_confirmation", with: "test" click_button "Update" expect(page).to have_content "User was successfully updated." expect(current_path).to eq user_path(user) end end context 'メールアドレスが未入力' do it 'ユーザーの編集が失敗する' do visit users_path click_link "Edit" fill_in "user_email", with: "" fill_in "user_password", with: "password" fill_in "user_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 can't be blank" expect(current_path).to eq user_path(user) end end context '登録済のメールアドレスを使用' do it 'ユーザーの編集が失敗する' do other_user = create(:user) visit users_path click_link "Edit" fill_in "user_email", with: other_user.email fill_in "user_password", with: "password" fill_in "user_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 user_path(user) end end 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 end describe 'マイページ' do context 'タスクを作成' do it '新規作成したタスクが表示される' do task = create(:task, user: user) visit user_path(user) click_link "Task list" expect(page).to have_content task.title expect(page).to have_content task.content expect(page).to have_content task.status expect(page).to have_link "Show" expect(page).to have_link "Edit" expect(page).to have_link "Destroy" expect(current_path).to eq tasks_path end end end end end
下記のようなセレクトボタンは、①のような表記ではなく下記のように記述することができる
select 'todo', from: 'Status' や within 'select[name="q[tag_id]"]' do # [name="q[tag_id]"]は、selectボタンの名前 select 'todo' end
②の部分を[expect(current_path).to eq task_pth(task)]のように記述すると、let(:task) { create(:task) }が呼ばれてletで生成されたtaskの詳細が表示されるので、今ここで作成したタスクの詳細が表示されない
[expect(current_path).to eq '/tasks/1']のように記述するのは、1つ目に生成されたtaskだからこのように記述できる
※ポイント
・下記のような記述をするとwithinで操作の範囲を設定できる(withinの中にクラスを指定して)
within('.breadcrumb') do expect(page).to have_link "Home" expect(page).to have_link "タグ" expect(page).to have_content "タグ編集" end
・switch_to_window(windows.last)で、タブが切り替わった場合は、タブを指定して検証できる。(この場合は、[windows.last]で最後のタブを指定している)
・RSpecの[it]を[xit]とするとpend扱いになり、他の箇所だけをテスト実行できる
・RSpecの[it]を[fit]とするとfocusしてピンポイントでテストを実行できる
・[be_truthy]の使い方
expectの中に記述したものが、trueであることを期待する場合に使う # 以下が例 expect(mail.present?).to be_truthy
・[be_falsey]の使い方
expectの中に記述したものが、falseであることを期待する場合に使う # 以下が例 expect(mail.present?).to be_falsey
・[fill_in]の使い方
[fill_in "フォームのid値 or フォームのラベルの名前", with "フォームに入力する内容"]
フォームのラベルの名前でうなくいかない時は、フォームのid値で指定する
※ポイント
inputタグにlabelやid属性、name属性がない場合は、以下のようにすれば使えるようになる。
ex)
下記のようになっている場合
<div>お名前</div> <input size="40" placeholder="サンプル太郎" " type="text"">
[spec/system/orders.spec.rb] find(:xpath, '//div[text()="お名前"]//following-sibling::input').set('テスト太郎') # xpathは、htmlの構造を把握する感じのもの # div[text()="お名前"]で、divタグの中の文字列が'お名前'の要素というもの # following-sibling::inputで、指定した要素(今回の場合は、divタグの中の文字列が'お名前')の次の要素で且つinputタグのものという意味 # setでそのフォームに文字列をセットする
expect(page).to have_select('ラベル名 or フィールドのid or フィールドのclass', selected: '選択されている文言')
expect(page).to have_selector(:css, '.form-control', text: '下書き') や expect(page).to have_selector("input[value=#{author.name}]"),
・確認ダイアログのrspec
上記のような確認ダイアログのrspecでの確認テストは、以下のようになる
it 'タスクの削除に成功する' do click_link "Destroy" expect(page.accept_confirm).to have_content "Are you sure?" # 確認ダイアログに表示されているメッセージを確認して、OKをクリック expect(page).to have_content "Task was successfully destroyed." # 確認ダイアログでOKをクリックした後の画面に表示されているメッセージを確認 expect(current_path).to eq tasks_path expect(page).not_to have_content task.title end
[accept_confirm]メソッド: Capybaraのメソッドで確認ダイアログのOKボタンを押す
[dismiss_confirm]メソッド: Capybaraのメソッドで確認ダイアログのキャンセルボタンを押す
確認ダイアログをクリックするRSpecテストは、下記のように記述することもできる
page.driver.browser.switch_to.alert.accept # OKボタンをクリック page.driver.browser.switch_to.alert.dismiss # キャンセルボタンをクリック
・[have_field]マッチャの使い方
[expect(page).to have_field "フォームのid値 or フォームのラベルの名前", with: "入力されている内容"]
・[have_http_status]マッチャの使い方
expect(page).to have_http_status(200) # HTTPのステータスコードをテストできる
・[attach_file]マッチャの使い方(画像のアップロード)
attach_file 'inputタグの画像アップロードのname部分 or labelのforの部分', "アップロードしたい画像のパス" # 以下が例 attach_file 'article[eye_catch]', "#{Rails.root}/spec/fixtures/images/test.jpeg" # 複数の画像をアップロードする時 attach_file 'article[eye_catch]', %w(spec/fixtures/images/test.png spec/fixtures/images/test2.png))
上記でアップロードした画像を検証するのは、以下の記述でできる
expect(page).to have_selector("img[src$='画像のファイル名']") # 以下が例 expect(page).to have_selector("img[src$='test.jpeg']")
・[have_checked_field]マッチャの使い方(ラジオボタンで選択されている状態)
expect(page).to have_checked_field '左寄せ'
・[have_unchecked_field]マッチャの使い方(ラジオボタンで選択されていない状態)
expect(page).to have_unchecked_field '中央'
・[choose]マッチャの使い方(ラジオボタンで指定したものを選択できる)
choose '左寄せ'
・[have_field]マッチャの使い方(フォームに入力されている値を検出できる)
expect(page).to have_field '(ラベル名)', with:(検出する値) # 以下が例 expect(page).to have_field '横幅', with: 200
・[sleep]マッチャの使い方(ブラウザ上で前の処理が完了するのに時間がかかり、ブラウザ上で処理をしている間にテストコードが進んでしまいテストが失敗するのを防ぐ為にsleepで指定した秒数テストを待つ)
sleep (秒数) # 以下が例(プレビューボタンをクリックした後にブラウザで表示されるまでに時間がかかる場合は、テストが失敗してしまうので以下の例のように2秒待つようにする) click_on('プレビュー') sleep 2 expect(page).to have_selector(".twitter-tweet")
・[click_button]マッチャの使い方(ブラウザ上でのbuttonをクリック(inputタグなど))
click_button('ボタンの表示名 or ボタンのid値') # 以下が例 click_button('更新する')
・[click_link]マッチャの使い方(ブラウザ上でのlinkをクリック(aタグなど))
click_link('リンクの表示名 or リンクのid値') # 以下が例 click_link('プレビュー')
・[click_on]マッチャの使い方(リンクかボタンか分からない時にどちらの場合でもクリックしてくれる便利なやつ)
click_on('リンクの表示名 or リンクのid値') # 以下が例 click_on('プレビュー')
・[click]マッチャの使い方(click_onなどがうまく機能しない時は、以下のようにして使える)
find('id or クラス名').click #以下が例 find('.search-button'').click
・画面に同じ表示名のボタンがある時に指定したボタンをクリックする方法
# ページ全体でclass名が[box-footer]になっている1つ目の[更新する]という表示名のボタンをクリック page.all('.box-footer')[0].click_button('更新する')
・要素の属性値を検証する方法
<input placeholder="料理をした日" class="me-1 cooking-memories-search-select-form" type="search" name="q[cooking_date_gteq]" id="q_cooking_date_gteq">
begin_cooking_date_search_form = find('#q_cooking_date_gteq') ① expect(begin_cooking_date_search_form['type']).to eq 'date'
※
①で要素を取得して要素["属性名"]
で属性値を取得できる
・ActionMailerのテスト(以下に例を示します)
let(:mail) { ArticleMailer.report_summary.deliver_now } # テストファイルでも[(メーラー名).(メソッド名).deliver_now]でメールを送信できる let(:check_sent_mail) { expect(mail.present?).to be_truthy expect(mail.to).to eq(['admin@example.com']) expect(mail.subject).to eq('公開済記事の集計結果') }
[mail.to]で送信先を検証できる
[mail.from]で送信元を検証できる
[mail.subject]でメールの件名を検証できる
[mail.body]でメールの本文を検証できる
・更新に失敗した時のURL
下記のように更新に失敗した時のURLは、updateアクションのURLになるので、rspecで更新に失敗した時点でのパスを確認するテストを記述する時は、画面に連られてeditアクションのパスを記載しないように注意する(更新に失敗した時は、編集画面をrenderしているだけであり、ルーティング処理を通らずupdateで失敗した時点で止まったままなので、updateのパスになっている)
・letの扱い
letは、letで定義している変数が呼ばれる度に新しくインスタンスを生成すると思っていたが、同じexample内(同じit内)で再度letを呼ぶ時は、最初に呼ばれた値をキャッシュして同じインスタンスを使い回すようになる
RSpec.describe "Tasks", type: :system do let(:task) { create(:task) } let(:user) { create(:user) } describe 'ログイン後' do before { login_as(user) } ① describe 'タスクの削除' do let!(:task) { create(:task, user: user) } ② it 'タスクの削除に成功する' do visit root_path click_link "Destroy" expect(page.accept_confirm).to have_content "Are you sure?" expect(page).to have_content "Task was successfully destroyed." expect(current_path).to eq tasks_path expect(page).not_to have_content task.title end end
上記のような場合に①で生成されたuserと②で使われているuserは、内容が同じuserになる
・下記のような[date_field]で作成したフォームのrspecテストは、以下のように記載できる。
fill_in "task_deadline", with: DateTime.new(2021, 8, 1, 10, 30)
上記の他にも下記のような記述方法がある
fill_in 'task_deadline', with: "00/2020/06/01/10:30"
fill_in 'Deadline', with: '002020-06-01-10:30'
基本的には、[DateTime.new]で記載するのが王道
・RSpecの日時表記の注意
RSpecで日時の表記などをテスト項目にする際は、以下のようにRSpecの日時表記をビューに合わせなければいけない
[app/views ファイル] task.deadline.strftime("%-m/%d %-H:%M")
[spec/system/task_spec.rb] it '既にステータスが完了のタスクのステータスを変更した場合、Taskの完了日が更新されないこと' do # TODO: FactoryBotのtraitを利用してください task = FactoryBot.create(:task, project_id: project.id, status: :done, completion_date: Time.current.yesterday) visit edit_project_task_path(project, task) select 'todo', from: 'Status' click_button 'Update Task' expect(page).to have_content('todo') expect(page).not_to have_content(Time.current.strftime('%Y-%m-%d')) expect(current_path).to eq project_task_path(project, task) end
[strftime]メソッドは、時刻を指定した文字列に従って文字列に変換し結果を返す
Time#strftime (Ruby 3.2 リファレンスマニュアル)
・rspecでの[transient]と[after(:build)]について
[spec/system/admin_articles_spec.rb] let(:article_with_tag) { create(:article, :with_tag, tag_name: 'Ruby') } ① let(:article_with_another_tag) { create(:article, :with_tag, tag_name: 'PHP') } it 'タグで絞り込み検索ができること' do article_with_tag ① article_with_another_tag visit admin_articles_path within 'select[name="q[tag_id]"]' do select 'Ruby' end click_button '検索' expect(page).to have_content(article_with_tag.title), 'タグでの検索ができていません' expect(page).not_to have_content(article_with_another_tag.title), 'タグでの絞り込みができていません' end
[spec/factories/articles.rb] factory :article do sequence(:title) { |n| "title-#{n}" } sequence(:slug) { |n| "slug-#{n}" } category end trait :with_tag do ② transient do sequence(:tag_name) { |n| "test_tag_name_#{n}" } sequence(:tag_slug) { |n| "test_tag_slug_#{n}" } end after(:build) do |article, evaluator| ③ article.tags << build(:tag, name: evaluator.tag_name, slug: evaluator.tag_slug) end end
上記のようなテストコードがある場合の動きは、以下になる。
また、articleとtagのアソシエーションは、以下のようになる
class Article < ApplicationRecord has_many :article_tags end class Tag <ApplicationRecord has_many :articles, through: :article_tags end
①でarticleがcreateされ、その時に(trait :with_tag)が指定されているので②が実行される。
[transient]は、属性に存在しないものを使いたい時に使う
③は、after(:build)でテスト用のデータを作成した後(buildになっているのは、railsでcreateするというのは、build→saveになる)に実行されるという事
③の中の部分は、以下のような動きになっている
article.tagsでアソシエーションを使って、生成したarticleに関連するtagという事
上記のarticle.tagsは、has_manyでアソシエーションされているので配列になるので、<<で配列に追加するイメージ(関連がbelongs_toの場合は、article.tagになると思うが、これは配列ではないので<<ではなく=で代入してあげるような記述になる)
build(:tag)でtagを生成して、その引数の(name: evaluator.tag_name, slug: evaluator.tag_slug)は、evaluator.tag_nameでtransientで生成した変数にアクセスしているようになる(下記がイメージ図)
64: sequence(:tag_slug) { |n| "test_tag_slug_#{n}" } 65: end 66: 67: after(:build) do |article, evaluator| 68: byebug => 69: article.tags << build(:tag, name: evaluator.tag_name, slug: evaluator.tag_slug) 70: end 71: end 72: 73: trait :with_sentence do (byebug) evaluator.tag_name "Ruby" # [test_tag_name_1]でないのは、①で[tag_name]を"Ruby"でオーバーライドしているため (byebug) evaluator.tag_slug "test_tag_slug_1"
[ドライバの種類]
Rspecで利用するドライバは、以下の3種類がある
・driven_by(:rack_test)
・driven_by(:selenium_chrome)
・driven_by(:selenium_chrome_headless)
それぞれの特徴は、以下のようになる
driven_by(:rack_test)
javascriptを使っている動作の確認ができない
httpステータスコードを確認するテストができる
driven_by(:selenium_chrome)
javascriptを含むテストを実現できる
このドライバを使用するとブラウザが実際に立ち上がり、画面上で動きが確認できる
httpステータスコードの確認ができない
driven_by(:selenium_chrome_headless)
javascriptを含むテストを実現できる
selenium_chromeのようにブラウザ上での動きが確認できない
httpステータスコードの確認ができない
上記のことから、通常時はjavascriptも実現できるselenium_chromeを使って、httpステータスコードを確認したい部分だけrack_testを使う
[テストファイルの作成]
下記のコマンドでテストファイルを作成できる
システムスペック(システムテストのファイル) $ rails g rspec:system ファイル名 モデルスペック(モデルテストのファイル) $ rails g rspec:model ファイル名
参考記事:
【Rails】『RSpec + FactoryBot + Capybara + Webdrivers』の導入&初期設定からテストの書き方まで | vdeep
Selenium WebDriverでRubyのテストを行う方法【初心者向け】 | TechAcademyマガジン
【RSpec】spec/rails_helper.rbを和訳&補足してみた - Qiita
【Rails】Rspecでテストコードを書く【78日目】|かわいかわ|note
【Rspec】Capybaraについて | プログラミングマガジン
RSpec letとlet!とbeforeの実行されるタイミング.|izumi haruya|note
rspec-rails 3.7の新機能!System Specを使ってみた - Qiita
特定のテストケースを実行したい時のfocus: true - その辺にいるWebエンジニアの備忘録
Read Everyday Rails - RSpecによるRailsテスト入門 | Leanpub
https://nyoken.com/rspec-feature-capybara
【RSpec/capybara】fill_inが上手く動作しない - Qiita
テストの基本構造 - RSpec/Capybara入門 - Ruby on Rails with OIAX
http://one-way.tech/programing/capybara-spec/
RSpec3でspec/support内のファイルを読み込む - Qiita
使えるRSpec入門・その4「どんなブラウザ操作も自由自在!逆引きCapybara大辞典」 - Qiita
【Rails】はじめてのSystemSpec(RSpec) - Qiita
RSpecコトハジメ ~初期設定マニュアル~ - Qiita
Pythonでブラウザ操作を自動化するSelenium WebDriverの使い方 – Valmore
【Rails】『RSpec + FactoryBot + Capybara + Webdrivers』の導入&初期設定からテストの書き方まで | vdeep
Rspec 複数のselectタグのテストを実行する時の注意 - Qiita
Capybara でデータ削除時の confirm ボタンを押したい - Just do IT
https://twei-blog.com/programming/ruby-on-rails/rspec/rspec-dialog/
have_fieldマッチャで指定した入力フィールドを確認する|haracaneのブログ
let, インスタンス変数, ローカル変数, 定数 in RSpec - Qiita
【Rspec】テストコードの処理の共通化(before、let、shared_examples) | プログラミングマガジン
Railsチュートリアル卒業者に捧げる、RSpec超入門 - 28歳からはじめるフリーランスLIFE!
【RSpec】テストの際、日付・時刻フォーム(datetime_select)に値を入力する - Qiita
【Rspec】System Spec(システムスペック)の基本 | プログラミングマガジン
【Rails入門】RSpecを使ったテスト方法を初心者向けに基本から解説 | 侍エンジニアブログ
CapybaraでJSのconfirmダイアログのボタンを押す - Qiita
【Rails】Selenium/RSpecでconfirmダイアログのテストをする - Qiita
Capybara + rspec で selected の状態を判定する : Rails - Qiita
使えるRSpec入門・その4「どんなブラウザ操作も自由自在!逆引きCapybara大辞典」 - Qiita
FactoryBot(FactoryGirl)チートシート - Qiita
https://twei-blog.com/programming/ruby-on-rails/rspec/rspec-picture/
[RSpec] System Specでおしゃれラジオボタンをチェックできないときの対処法 - Qiita
使えるRSpec入門・その4「どんなブラウザ操作も自由自在!逆引きCapybara大辞典」 - Qiita
以下の記事が凄く分かりやすく見やすい Capybaraチートシート - Qiita