SystemSpecを書いてみよう
前回の続きで今回はSystemSpec
を書いてみます。
SystemSpecとは
システムテストのことをRspecではシステムスペックと言います。
システムテストとは、実際に使用される状況と同じ設定でテストを行い、想定通りに動作するか検証することを言います。
テストの作成
spec/system/user_spec.rbを作成し、ここにユーザーに関するテストを書いていきます。
require 'rails_helper' RSpec.describe "Users", type: :system do end
大きく分けて
・ページ遷移
・ユーザー新規作成
・ユーザー編集
・ユーザー削除
の4つに関するテストを作ります。
describe
に対象、context
に条件、it
に具体的なテスト内容を記述します。
require 'rails_helper' RSpec.describe "Users", type: :system do describe 'ページ遷移確認' do end describe 'ユーザー新規作成' do end describe 'ユーザー編集' do end describe 'ユーザー削除' do end end
まずは対象で分けてみました。
次はdescribeの中にcontextで条件を書いていきます。
require 'rails_helper' RSpec.describe "Users", type: :system do describe 'ページ遷移確認' do context 'ユーザーの一覧ページに遷移' do end context 'ユーザーの新規作成ページに遷移' do end context 'ユーザーの編集ページに遷移' do end context 'ユーザーの詳細ページに遷移' do end end describe 'ユーザー新規作成' do context 'フォームの入力値が正常な場合' do end context '名前が未入力の場合' do end end describe 'ユーザー編集' do context 'フォームの入力値が正常な場合' do end context '名前が未入力の場合' do end end describe 'ユーザー削除' do end end
こんな感じになりました。
テストは正常系
と異常系
を作成します。
正常系は、全てのフォームが入力されている場合など、想定通りの操作をしたときの挙動を確認します。
異常系は、必須項目のフォームが空の場合など、想定外の操作をしたときの挙動を確認します。
なおユーザー削除は必ず成功する想定なので正常系のみです。
ページ遷移確認のテスト
RSpec.describe "Users", type: :system do let(:user) { create(:user) } describe 'ページ遷移確認' do context 'ユーザーの一覧ページに遷移' do it 'ユーザーの一覧ページへのアクセスに成功する' do user_list = create_list(:user, 3) visit users_path expect(page).to have_content 'ユーザー一覧' expect(page).to have_content user_list[0].name expect(page).to have_content user_list[1].name expect(page).to have_content user_list[2].name expect(current_path).to eq users_path end end context 'ユーザーの新規作成ページに遷移' do it 'ユーザーの新規作成ページへのアクセスに成功する' do visit new_user_path expect(page).to have_content 'ユーザー新規作成' expect(current_path).to eq new_user_path end end context 'ユーザーの編集ページに遷移' do it 'ユーザーの編集ページへのアクセスに成功する' do visit edit_user_path(user) expect(page).to have_content 'ユーザー編集' expect(page).to have_field 'Name', with: user.name expect(page).to have_field 'Gender', with: user.gender expect(current_path).to eq edit_user_path(user) end end context 'ユーザーの詳細ページに遷移' do it 'ユーザーの詳細ページへのアクセスに成功する' do visit user_path(user) expect(page).to have_content 'ユーザー詳細' expect(page).to have_content user.name expect(page).to have_content user.gender expect(current_path).to eq user_path(user) end end end end
例として2つ目のテストを挙げると
・visit new_user_path
ユーザー新規作成ページを訪れ
・expect(page).to have_content 'ユーザー新規作成'
そのページに「ユーザー新規作成」と表示されていて
・expect(current_path).to eq new_user_path
今いるpathがnew_user_path
ならテストが通ります。
let(:user) { create(:user) }
は、itの中でその都度user = create(:user)
でuserを作成するのではなく、予め定義だけしておいてuser
で呼び出せるようにしています。
残りのテスト
全部説明すると長くなってしまうので残り一気にいっちゃいます。
最終的なコードがこちら。
require 'rails_helper' RSpec.describe "Users", type: :system do let(:user) { create(:user) } describe 'ページ遷移確認' do context 'ユーザーの一覧ページに遷移' do it 'ユーザーの一覧ページへのアクセスに成功する' do user_list = create_list(:user, 3) visit users_path expect(page).to have_content 'ユーザー一覧' expect(page).to have_content user_list[0].name expect(page).to have_content user_list[1].name expect(page).to have_content user_list[2].name expect(current_path).to eq users_path end end context 'ユーザーの新規作成ページに遷移' do it 'ユーザーの新規作成ページへのアクセスに成功する' do visit new_user_path expect(page).to have_content 'ユーザー新規作成' expect(current_path).to eq new_user_path end end context 'ユーザーの編集ページに遷移' do it 'ユーザーの編集ページへのアクセスに成功する' do visit edit_user_path(user) expect(page).to have_content 'ユーザー編集' expect(page).to have_field 'Name', with: user.name expect(page).to have_field 'Gender', with: user.gender expect(current_path).to eq edit_user_path(user) end end context 'ユーザーの詳細ページに遷移' do it 'ユーザーの詳細ページへのアクセスに成功する' do visit user_path(user) expect(page).to have_content 'ユーザー詳細' expect(page).to have_content user.name expect(page).to have_content user.gender expect(current_path).to eq user_path(user) end end end describe 'ユーザー新規作成' do before { visit new_user_path } context 'フォームの入力値が正常な場合' do it 'ユーザーの新規作成が成功する' do fill_in 'Name', with: 'test_name' select '男性', from: 'Gender' click_button '登録する' expect(page).to have_content 'User was successfully created.' expect(current_path).to eq '/users/1' end end context '名前が未入力の場合' do it 'ユーザーの新規作成が失敗する' do fill_in 'Name', with: nil select '男性', from: 'Gender' click_button '登録する' expect(page).to have_content '1 error prohibited this user from being saved:' expect(page).to have_content 'Nameを入力してください' expect(current_path).to eq users_path end end end describe 'ユーザー編集' do before { visit edit_user_path(user) } context 'フォームの入力値が正常な場合' do it 'ユーザーの編集が成功する' do fill_in 'Name', with: 'update_name' select '男性', from: 'Gender' click_button '更新する' expect(page).to have_content 'User was successfully updated.' expect(current_path).to eq '/users/1' end end context '名前が未入力の場合' do it 'ユーザーの編集が失敗する' do fill_in 'Name', with: nil select '男性', from: 'Gender' click_button '更新する' expect(page).to have_content '1 error prohibited this user from being saved:' expect(page).to have_content 'Nameを入力してください' expect(current_path).to eq user_path(user) end end end describe 'ユーザー削除' do let!(:user) { create(:user) } it 'ユーザーの削除が成功する' do visit users_path click_link 'Destroy' expect(page.accept_confirm).to eq 'Are you sure?' expect(page).to have_content 'User was successfully destroyed.' expect(current_path).to eq users_path end end end
・ユーザー新規作成のbefore { visit new_user_path }
describe 'ユーザー新規作成'
内の2つのcontextは両方ともまずnew_user_pathにアクセスするところから始まります。それぞれのテストにいちいち書くのはDRYに反しますね。なのでbeforeでまとめてしまいましょう。ユーザー編集のbeforeも同様です。
・ユーザー削除のlet!(:user) { create(:user) }
先ほどのletと違い、!がついています。
2つの違いはuserが作成されるタイミングです。
・letはuserが呼び出されたときに作成され、
・let!はテスト実行前に作成されます。
はい、よくわかりません。
具体的に見てみます。ユーザー編集の正常系のテストはこのようになっています。
RSpec.describe "Users", type: :system do let(:user) { create(:user) } 略 describe 'ユーザー編集' do before { visit edit_user_path(user) } context 'フォームの入力値が正常な場合' do it 'ユーザーの編集が成功する' do fill_in 'Name', with: 'update_name' select '男性', from: 'Gender' click_button '更新する' expect(page).to have_content 'User was successfully updated.' expect(current_path).to eq '/users/1' end end
該当のユーザーの編集ページを訪れ
Nameに「update_name」と入力し
Genderは「男性」を選択
「更新する」ボタンをクリックすると
ページに「User was successfully updated.」と表示されていて
そのページのpathは「/users/1」になっている
とテストが成功する。
という内容です。
この最初のvisit edit_user_path(user)、ここでuser
が出てきてます。このタイミングで実際にcreate(:user)されています。
次にユーザー削除のテストを見てみましょう。
describe 'ユーザー削除' do let!(:user) { create(:user) } it 'ユーザーの削除が成功する' do visit users_path click_link 'Destroy' expect(page.accept_confirm).to eq 'Are you sure?' expect(page).to have_content 'User was successfully destroyed.' expect(current_path).to eq users_path end end
こちらはlet!(:user) { create(:user) }
としてあります。
このテストは
users_path(ユーザー一覧ページ)を訪れ
「Destroy」というリンクをクリック
アラートで「Are you sure?」と表示され
OKすると「User was successfully destroyed.」と表示され
users_pathに遷移される
とテスト成功です。
が、itの中を見てみるとuser
はどこにも呼び出されていません。
しかしそもそもユーザーが1件もないとDestroyというリンクは表示されないような実装にしているので、itの中でuserを作成する必要があります。
let(:user) { createa(:user) }
と定義しているだけではuserは作成されないので、let!(:user) { create(:user) }
でテスト実行前に予め作成しておかないといけません。
ユーザー削除のテストの結果をletとlet!で見比べてみます。
letの場合
let!だと想定通り、テストが通りますが、
letの場合
「Destroy」なんて見つからない、と言われてしまいます。
前述の通り、ユーザーが0件だと「Destroy」が表示されない、つまりユーザーが作成されていない。
削除のテストを実行するためにはやはりletではなくlet!でユーザーの作成が必要だとわかります。
最後に
簡単なCRUDアプリなのでシンプルなテストになりました。もっと多機能なアプリだとテストも複雑になりそうです。あとテストではハードコーディングしたほうがいいのかなー、と思ったんですがどうなんでしょう。テスト件数が増えるとメンテナンスが大変になりそうですがそっちの方が堅牢なテストになりそうな気もします。
今回も自分にとってしっくり来る表現をしたので厳密には間違ってたりするかもしれません。ご指摘あればコメントいただけると嬉しいです。
長くなりましたが、読んでいただきありがとうございました。
参考サイト
使えるRSpec入門・その4「どんなブラウザ操作も自由自在!逆引きCapybara大辞典」 - Qiita
Module: Capybara::Node::Actions — Documentation for jnicklas/capybara (master)
システムテストとは?開発段階のテストの流れと主な種類|発注成功のための知識が身に付く【発注ラウンジ】<
RSpecの(describe/context/example/it)の使い分け - Qiita
【直感的に書ける!】RspecでRubyのテストを覚えよう! | プロぽこ
RSpec の letとlet!とbeforeの挙動と実行される順番 - Qiita