RSpecのsystemテストでドロップダウンリストを選択させるテストを記述
自作アプリについて、自分で実装したいと思った機能はおおかた実装しましたので、テストコードを書きました。
テストを書くにあたって、やや苦労した部分があったので備忘録がてら書いておきます。
テストしたいこと
「ほしいものリスト」から2つのitemsを選択し、2つのitem_idをとってComparisonsテーブルに保存する。このとき、以下の条件を満たすことを確認したい。
存在する2つのアイテムを比較できる
片方のアイテムが空だと比較できない
両方同じアイテムだと比較できない
過去に作成した比較と同一の場合は、新規作成されずに既存の比較ページに飛ぶ(この挙動自体はコントローラとモデルで記述済み)
自分で作った比較は削除できる
自分以外のユーザーの比較は表示されず、削除もできない
書いたコード
require 'rails_helper'
RSpec.describe "Comparisons", type: :system do
before do
# ユーザーを1人作成
@user = FactoryBot.create(:user)
# そのユーザーのアイテムを2個作成
@item1 = FactoryBot.create(:item, user_id: @user.id, name: 'アイテム1')
@item2 = FactoryBot.create(:item, user_id: @user.id, name: 'アイテム2')
end
describe '比較機能のテスト' do
context '正しく登録できるケース' do
it 'primary_item_id, secondary_item_idがあれば登録できる' do
# ログインする
visit new_user_session_path
fill_in 'Email', with: @user.email
fill_in 'Password', with: @user.password
find('input[name="commit"]').click
sleep 1
expect(current_path).to eq items_path
# 比較新規登録画面へのボタンがある
expect(page).to have_content('比較する')
expect(
all('.nav-item')[2].click
).to have_content('比較を作成')
# 比較新規登録画面に遷移する
visit new_comparison_path
# primary_item_id, secondary_item_idをドロップダウンから選択する
within('form') do
select @item1.name, from: '1つめのアイテム'
select @item2.name, from: '2つめのアイテム'
end
expect {
click_button '作成'
sleep 1
}.to change { Comparison.count }.by(1)
end
end
context '登録できないケース' do
it 'primary_item_idが空だと登録できない' do
# ログインする
visit new_user_session_path
fill_in 'Email', with: @user.email
fill_in 'Password', with: @user.password
find('input[name="commit"]').click
sleep 1
expect(current_path).to eq items_path
# 比較新規登録画面へのボタンがある
expect(page).to have_content('比較する')
expect(
all('.nav-item')[2].click
).to have_content('比較を作成')
# 比較新規登録画面に遷移する
visit new_comparison_path
# secondary_item_idをドロップダウンから選択する
within('form') do
select @item2.name, from: '2つめのアイテム'
end
expect {
click_button '作成'
sleep 1
}.to change { Comparison.count }.by(0)
end
it 'secondary_item_idが空だと登録できない' do
# ログインする
visit new_user_session_path
fill_in 'Email', with: @user.email
fill_in 'Password', with: @user.password
find('input[name="commit"]').click
sleep 1
expect(current_path).to eq items_path
# 比較新規登録画面へのボタンがある
expect(page).to have_content('比較する')
expect(
all('.nav-item')[2].click
).to have_content('比較を作成')
# 比較新規登録画面に遷移する
visit new_comparison_path
# primary_item_idをドロップダウンから選択する
within('form') do
select @item1.name, from: '1つめのアイテム'
end
expect {
click_button '作成'
sleep 1
}.to change { Comparison.count }.by(0)
end
it 'primary_item_idとsecondary_item_idが同じだと登録できない' do
# ログインする
visit new_user_session_path
fill_in 'Email', with: @user.email
fill_in 'Password', with: @user.password
find('input[name="commit"]').click
sleep 1
expect(current_path).to eq items_path
# 比較新規登録画面へのボタンがある
expect(page).to have_content('比較する')
expect(
all('.nav-item')[2].click
).to have_content('比較を作成')
# 比較新規登録画面に遷移する
visit new_comparison_path
# primary_item_id, secondary_item_idをドロップダウンから選択する
within('form') do
select @item1.name, from: '1つめのアイテム'
select @item1.name, from: '2つめのアイテム'
end
expect {
click_button '作成'
sleep 1
}.to change { Comparison.count }.by(0)
end
it '同じ組み合わせの比較は新しく作成されない' do
# ログインする
visit new_user_session_path
fill_in 'Email', with: @user.email
fill_in 'Password', with: @user.password
find('input[name="commit"]').click
sleep 1
expect(current_path).to eq items_path
# 比較新規登録画面へのボタンがある
expect(page).to have_content('比較する')
expect(
all('.nav-item')[2].click
).to have_content('比較を作成')
# 比較新規登録画面に遷移する
visit new_comparison_path
# primary_item_id, secondary_item_idをドロップダウンから選択する
within('form') do
select @item1.name, from: '1つめのアイテム'
select @item2.name, from: '2つめのアイテム'
end
expect {
click_button '作成'
sleep 1
}.to change { Comparison.count }.by(1)
# 比較新規登録画面に遷移する
visit new_comparison_path
# primary_item_id, secondary_item_idをドロップダウンから選択する
within('form') do
select @item1.name, from: '1つめのアイテム'
select @item2.name, from: '2つめのアイテム'
end
expect {
click_button '作成'
sleep 1
}.to change { Comparison.count }.by(0)
end
end
context '比較削除' do
it '比較を削除できる' do
# 比較を作成
@comparison = Comparison.create(primary_item_id: @item1.id, secondary_item_id: @item2.id, user_id: @user.id)
# ログインする
visit new_user_session_path
fill_in 'Email', with: @user.email
fill_in 'Password', with: @user.password
find('input[name="commit"]').click
sleep 1
# 比較一覧画面に遷移する
visit comparisons_path
# 比較削除ボタンがある
expect(page).to have_content('削除')
expect {
page.accept_confirm do
find_link('削除', href: comparison_path(@comparison.id)).click
end
sleep 1
}.to change { Comparison.count }.by(-1)
end
it '自分以外のユーザーの比較詳細は閲覧できず、削除もできない' do
@comparison = Comparison.create(primary_item_id: @item1.id, secondary_item_id: @item2.id, user_id: @user.id)
# 別のユーザーを作成
@user2 = FactoryBot.create(:user)
# ログインする
visit new_user_session_path
fill_in 'Email', with: @user2.email
fill_in 'Password', with: @user2.password
find('input[name="commit"]').click
sleep 1
expect(current_path).to eq items_path
# 比較一覧画面に遷移する
visit comparisons_path
# 比較削除ボタンがない
expect(page).to have_no_content('削除')
# URLを直接入力しても遷移できない
visit comparison_path(@comparison.id)
expect(current_path).to eq comparisons_path
end
end
end
end
結果
苦労したところ
RSpec自体は好きなんですが、どうも今だにインプット・ボタン系の要素の操作をどのように指示するか、ちゃんと把握しきれていません。
具体的には、今回めちゃくちゃ詰まったのは、最初の「アイテム1と2をドロップダウンから選ぶ」ところの選択と、最後の「削除ボタンを押す」という動作。
悪戦苦闘の末、ドロップダウン選択はこれでいけました。
within('form') do
select @item1.name, from: '1つめのアイテム'
select @item2.name, from: '2つめのアイテム'
end
削除ボタンは下のような感じ。
わかってしまえば「そりゃそう」なんだけど、ゼロベースでなかなか書けなかったなぁ。
expect {
page.accept_confirm do
find_link('削除', href: comparison_path(@comparison.id)).click
end
sleep 1
}.to change { Comparison.count }.by(-1)
上記のコードを日本語に直すと、
テストコードはできるだけがっちり書こう
実は上記のテストコード作成の途中で気づいたのですが、「他人が作った比較ページにURL直打ちで飛べちゃう」という設定ミスがありました。本番サービスだったら大事故です。
テストコードのおかげで発覚しました。
テストコードは、「このアプリの機能ってこうだよな」「この動きはされちゃあ困るよな」という要望を、相当の再現性を伴いつつ担保してくれる素敵な仕組みだと思います。
なにより、個人開発の場合は今回のように考慮漏れの部分に気づくチャンスでもあるので、テストコードはしっかりと書くのが良いでしょう。
で、上のコードのように絶望的に長いコードに見えるんですが、ぶっちゃけ「なぜ、何をテストするのか」という意図がわかっていれば、細かい記述は今後どんどんAI任せになったりするでしょう。
そうなれば、人間はもうすこし抽象的に「こんなテストやってくれよ」って考える役割に集中できるので、テストコードの記述については「記述方法の暗記」よりも「テスト自体の設計の練習」を意識するのが良いと感じました。
今日は以上です。最後までお読みいただきありがとうございました。
この記事が気に入ったらサポートをしてみませんか?