#はじめに
こちらは前回の記事【第2回】RSpecビギナーが、ビギナーなりにSystemSpecを書いてみた(沼編) の続き、SystemSpec(システムスペック)完結編です。
ModelSpec(モデルスペック)について知りたい方はこちらをご覧下さい。
また先日、RSpecの雄である伊藤淳一 @jnchito さんのご厚意で開催された初学者向けの勉強会(RSpecビギナーズ!!)にも参加致しましたので、こちらの動画も見て頂けるとより理解が深まると思うので、もしよろしければご覧下さい。
#この記事で扱うこと
-
SystemSpec(システムスペック)
モデル、コントローラ、ビュー、全部テストできるよ!
-
システムスペックの具体的な記述例
自身のポートフォリオを参考にして記述していきます。今回扱うのは記事の投稿、編集、DM、通知のテストです。
#この記事で扱わないこと
-
ModelSpec(モデルスペック)
こちらをご覧下さい。 -
RSpecのセットアップ、準備
(後述する参考書籍『EverydayRails-RSpecによるRailsテスト入門』にて詳しく記載してあるのでそちらを参考にしてください)
#前提
-
対象
RSpec書こうとしてるけど何が何だかさっぱりなんじゃあ〜という初学者の方。
ただ、伊藤さんの使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」
こちらの記事内容をある程度は見ていたり、なんかやったことはあるな〜とか、最低限describe,it,expectの役割が分かる方が望ましいです。 -
参考コード
前述したように自身のポートフォリオを参考に記述するので、こちらにGitHubのリンクを貼っておきますが、テストの対象はサイトの基幹機能に絞っていますのでご了承ください。
【サイトの基幹機能】(個人・法人会員の新規登録/ログイン/編集、法人会員登録の申請、記事の投稿/編集、DM、通知など) -
テストを記述するための準備
RSpecによるテストを記述するためには、gem 'rspec-rails'をはじめ、いくつかgemを入れたり設定をする必要があります。
まずはテストを書く準備を整えてからお読みください。
必要なものは『EverydayRails-RSpecによるRailsテスト入門』に記載してあります。
というか、これを見ればこの記事を見なくても分かる人は分かると思います。
具体例としてコードを見たいという方はそのままお読みいただけると嬉しいです。(本当に参考程度ですが)
#articles_spec.rbのテスト
###①FactoryBotを使用し、articleデータをあらかじめ用意しておく
おなじみのFactoryBotを使用し事前データを用意します。第1回の時に作成したものですが、システムスペックを書く際にも使用するので、参考までに以下に載せておきます。
FactoryBotの詳細は、モデルスペックの記事を参考にしてください。
FactoryBot.define do
#FactoryBotを使用し、articleデータをあらかじめ用意しておく
factory :article do
title { "テストタイトル" }
body { "テスト本文" }
is_active { true }
company
genre
end
end
###②具体的なコードの記述
それではテストコードを書いていきます。
$ rails g rspec:system articles を実行すると spec/system フォルダ内に articles_spec.rb が作成されます。
テストコード(実際のブラウザ上の動きなど)はこちらのファイルに記述していきます。
以下、完成例です。
require 'rails_helper'
RSpec.describe "Articles", type: :system do
describe '記事のテスト' do
let(:company){FactoryBot.create(:company)}
let(:genre){FactoryBot.create(:genre)}
let!(:article){FactoryBot.create(:article, company: company, genre: genre)}
before do
visit new_company_session_path
fill_in 'メールアドレス', with: company.email
fill_in 'パスワード', with: company.password
click_button 'ログイン'
end
describe 'サイドバーのテスト' do
context '表示の確認' do
it '記事検索と表示される' do
expect(page).to have_content "記事検索"
expect(current_path).to eq corporate_articles_path
end
it '記事登録と表示される' do
expect(page).to have_content "記事登録"
expect(current_path).to eq corporate_articles_path
end
end
end
describe '投稿のテスト' do
context '新規記事投稿ページへ遷移' do
it '遷移する' do
click_on '記事を登録する'
expect(page).to have_content "新規記事投稿"
expect(current_path).to eq new_corporate_article_path
end
end
context '表示の確認' do
before do
visit new_corporate_article_path
end
it '記事トップ画像フォームが表示される' do
expect(page).to have_field 'article[image]'
end
it 'ジャンルのセレクトボックスが表示される' do
expect(page).to have_select 'ジャンル'
end
it '掲載ステータスのセレクトボックスが表示される' do
expect(page).to have_select '掲載ステータス'
end
it '記事タイトルフォームが表示される' do
expect(page).to have_field '記事タイトル'
end
it '記事内容フォームが表示される' do
expect(page).to have_field '記事内容'
end
it '記事投稿ボタンが表示される' do
expect(page).to have_button '記事を投稿する'
end
end
context '記事の投稿' do
before do
visit new_corporate_article_path
end
it '投稿に成功する' do
select "テストジャンル", from: 'ジャンル'
select "掲載中", from: '掲載ステータス'
fill_in '記事タイトル', with: "RSpecは難しい"
fill_in '記事内容', with: "難しいけどテストスイートが通って全部緑になると嬉しい"
click_button "記事を投稿する"
expect(page).to have_content "記事を新規投稿しました。"
end
it '投稿に失敗する' do
fill_in '記事タイトル', with: ""
click_button "記事を投稿する"
expect(page).to have_content "件のエラーが発生したため 記事 は保存されませんでした。"
end
end
end
describe '編集のテスト' do
context '各画面への遷移の確認' do
it '記事詳細画面への遷移ができる' do
click_on article.title
expect(page).to have_content "記事詳細"
expect(current_path).to eq corporate_article_path(article)
end
it '記事編集画面への遷移ができる' do
visit corporate_article_path(article) # 記事詳細画面への遷移
click_on "編集する"
expect(page).to have_content "記事情報編集"
expect(current_path).to eq edit_corporate_article_path(article)
end
end
context '表示及び編集の確認' do
before do
visit edit_corporate_article_path(article)
end
it '記事トップ画像フォームが表示される' do
expect(page).to have_field 'article[image]'
end
it 'ジャンルのセレクトボックスが表示される' do
expect(page).to have_select('ジャンル', selected: 'テストジャンル')
end
it '掲載ステータスのセレクトボックスが表示される' do
expect(page).to have_select('掲載ステータス', selected: '掲載中')
end
it '記事タイトルフォームが表示される' do
expect(page).to have_field '記事タイトル', with: article.title
end
it '記事内容フォームが表示される' do
expect(page).to have_field '記事内容', with: article.body
end
it '記事編集ボタンが表示される' do
expect(page).to have_button '変更を保存する'
end
it '編集に成功する' do
select "掲載停止中"
fill_in '記事タイトル', with: "RSpecをスラスラ書けるようになるまで掲載停止します"
click_button '変更を保存する'
expect(page).to have_content '記事情報の更新が完了しました。'
expect(page).to have_content '【現在の掲載状況:掲載停止中】'
expect(current_path).to eq corporate_article_path(article)
end
it '編集に失敗する' do
fill_in '記事タイトル', with: ""
click_button '変更を保存する'
expect(page).to have_content "件のエラーが発生したため 記事 は保存されませんでした。"
end
end
end
end
end
モデルスペックの回でも説明しましたが、記事の投稿は
⑴会社が居て、⑵記事のジャンルがあり、⑶記事が投稿できる
という流れになっています。
そのため、記事投稿をするには事前に会社が存在しジャンルを選択する必要がある、ということは理解できるかと思います。前回と異なるのは、FactoryBotで作成したデータはインスタンス変数ではなく、letを使い保存していることに注意してください。
それでは、記事の投稿のテストに関する説明をしていきます。 表示のテストに関しては完成コード例をご覧下さい。恐らくこの第3回まで読んでくれているあなたなら説明せずともわかるはず。have_◯◯の部分だけいくつか種類があるのでご注意ください。 以下のコード例をご覧下さい。
context '記事の投稿' do
before do
visit new_corporate_article_path
end
it '投稿に成功する' do
select "テストジャンル", from: 'ジャンル' # セレクトボックスからジャンルを選択
select "掲載中", from: '掲載ステータス' # セレクトボックスから掲載ステータス(掲載中 or 掲載停止中)を選択
fill_in '記事タイトル', with: "RSpecは難しい" # タイトルを入力
fill_in '記事内容', with: "難しいけどテストスイートが通って全部緑になると嬉しい" # 記事内容を入力
click_button "記事を投稿する" # 記事を投稿するボタンを押下
expect(page).to have_content "記事を新規投稿しました。"
end
it '投稿に失敗する' do
fill_in '記事タイトル', with: ""
click_button "記事を投稿する"
expect(page).to have_content "件のエラーが発生したため 記事 は保存されませんでした。"
end
end
上記のコード例では、まずbeforeブロック内で投稿フォームが表示されているページへ遷移しています。
次に投稿フォーム1.ジャンルの選択、2.掲載ステータスの選択、3.タイトルの入力、4.記事内容の入力をし投稿ボタンの押下します。
つまづきポイント「セレクトボックスの選択」
上記のセレクトボックスの選択ではselect "テストジャンル", from: 'ジャンル'
という記述になっています。
恐らく普通にやればつまづくところではないのかもしれませんが、私はここでもかなり苦戦しましたので共有致します。
つまづいた原因は、labelタグの書き方が正しい書き方になっていなかったことで、上手くセレクトボックスを選択することができませんでした。
私の間違ったlabelタグの書き方がこちら。
<%= f.label :ジャンル %><br>
<%= f.collection_select :genre_id, Genre.all, :id, :genre_name, include_blank: "--選択してください--" %>
この<%= f.label :ジャンル %>
という記述が間違っています。
ブラウザ上は特に問題なく表示されるのですが、検証ツールで確認すると
<label for="article_ジャンル">ジャンル</label>
というように、for属性がなぜか英語と日本語が混ざった表示(for="article_ジャンル")になっており、これだと上手く選択ができないようです。
正しいlabelタグの書き方がこちら。
<%= f.label :genre_id, "ジャンル" %><br>
<%= f.collection_select :genre_id, Genre.all, :id, :genre_name, include_blank: "--選択してください--" %>
2020.10.2追記
上記labelタグの書き方ですが、"ジャンル"という形でビューにベタ書きするのではなく、YAMLファイルに翻訳したものを入れておき、ビューでは表示するだけという形にした方が良さそうです。一旦、この書き方で記載しますが、後に修正予定です。
検証ツールで見たときの表示は<label for="article_genre_id">ジャンル</label>
というようになっています。
テストの記述というよりは、そもそものビューの書き方が間違っていたということで、地味な部分かもしれませんが、第2回の記事のコメントでアドバイスいただいた際にやっと原因が分かりました。
あまりこれで苦労する方は居られないかもしれませんが、一応こちらの記事【HTML】lavelタグにfor属性を付けるべき理由とは?使い方まで徹底解説!でlabelタグについて詳しく解説してあります。
恐らく、for属性にちゃんとした値(今回の場合 genre_id)が入っていればinput要素にもプログラム的に紐付けされる、というところなんでしょうね。勉強になりました。
私のようになぜか上手くテストが通らないよ〜という時は、Chromeの検証ツールで確認してみるといいかもですね!
残りの投稿に失敗するテストや、記事の編集のテストは今までと基本は同じなので、完成コードを参考に記述してみてください!
#rooms_spec.rbのテスト
###①具体的なコードの記述
いよいよテストも大詰めです!こちらのDM・通知のテストで基幹機能のテストは終わりになります。頑張りましょう!
ちなみにこちらのテストではFactoryBotは使用しません。以下、完成コード例です。
require 'rails_helper'
RSpec.describe "Rooms", type: :system do
let(:user){FactoryBot.create(:user)}
let!(:company){FactoryBot.create(:company)}
describe 'DMのテスト' do
before do
visit new_user_session_path
fill_in 'メールアドレス', with: user.email
fill_in 'パスワード', with: user.password
click_button 'ログイン'
expect(page).to have_content 'ログインしました。'
end
context '表示の確認' do
it '専門家一覧に遷移ができる' do
click_on '専門家'
expect(current_path).to eq companies_path
end
it '企業詳細画面に遷移しDMを始めるボタンが表示される' do
visit companies_path
click_on 'テスト株式会社'
expect(current_path).to eq company_path(company)
expect(page).to have_content 'テスト株式会社'
expect(page).to have_button 'DMを始める'
end
end
context '個人側:メッセージの送信' do
before do
visit company_path(company) # 企業の詳細画面へ移動
click_on 'DMを始める'
end
it 'チャットルームに入ることができ、送信フォームが表示される' do
expect expect(page).to have_field 'メッセージを入力して下さい'
end
it 'メッセージを送信できる' do
fill_in 'メッセージを入力して下さい', with: 'テストメッセージ'
click_button '送信'
expect(page).to have_content 'テストメッセージ'
end
it 'メッセージ送信後、DM一覧画面に送信履歴が追加される' do
fill_in 'メッセージを入力して下さい', with: 'テストメッセージ'
click_button '送信'
expect(page).to have_content 'テストメッセージ' # チャットルーム内のメッセージ
visit rooms_path
expect(page).to have_content 'テストメッセージ' # DM一覧画面のメッセージ
expect(page).to have_link 'メッセージを見る'
end
end
context '法人側:メッセージの受信〜送信(返信)' do
before do
# 個人側でメッセージ送信
visit company_path(company)
click_on 'DMを始める'
fill_in 'メッセージを入力して下さい', with: 'テストメッセージ'
click_button '送信'
logout(user)
# 法人ログイン
visit new_company_session_path
fill_in 'メールアドレス', with: company.email
fill_in 'パスワード', with: company.password
click_button 'ログイン'
expect(page).to have_content 'ログインしました。'
end
it 'ヘッダーに通知数が表示される' do
expect(page).to have_content '1 通知'
end
it 'リンクから通知一覧画面に遷移でき、個人ユーザーからのメッセージの受信が確認できる' do
click_on '通知'
expect(page).to have_content '通知'
expect(page).to have_content 'テスト 太郎さんからのメッセージがあります'
end
it 'リンクからチャットルームに入ることができ、受け取ったメッセージが表示される' do
click_on '通知'
click_on 'メッセージ'
expect expect(page).to have_content 'テストメッセージ'
end
it 'メッセージを送信できる' do
click_on '通知'
click_on 'メッセージ'
fill_in 'メッセージを入力して下さい', with: 'テストメッセージへの返信'
click_button '送信'
expect(page).to have_content 'テストメッセージへの返信'
end
end
end
end
DM・通知のテストでは個人側と法人側で分けて説明していきたいと思います。 まずは**個人側のテスト**です。以下のコード例をご覧下さい。
####【個人側】 DM・通知のテスト
context '個人側:メッセージの送信' do
before do
visit company_path(company) # 企業の詳細画面へ移動
click_on 'DMを始める'
end
it 'チャットルームに入ることができ、送信フォームが表示される' do
expect expect(page).to have_field 'メッセージを入力して下さい'
end
it 'メッセージを送信できる' do
fill_in 'メッセージを入力して下さい', with: 'テストメッセージ'
click_button '送信'
expect(page).to have_content 'テストメッセージ'
end
it 'メッセージ送信後、DM一覧画面に送信履歴が追加される' do
fill_in 'メッセージを入力して下さい', with: 'テストメッセージ'
click_button '送信'
expect(page).to have_content 'テストメッセージ' # チャットルーム内のメッセージ
visit rooms_path
expect(page).to have_content 'テストメッセージ' # DM一覧画面のメッセージ
expect(page).to have_link 'メッセージを見る'
end
end
特に難しいポイントなどはないかと思います。
**'メッセージを送信できる'**というテストでは、
1.fill_in 'メッセージを入力して下さい', with: 'テストメッセージ'
⇨テストメッセージという文字列をテキストボックスに入力
2.click_button '送信'
⇨送信ボタンを押下
3.expect(page).to have_content 'テストメッセージ'
⇨同ページ内でテストメッセージという文字列があることを期待することで、メッセージが送信されたことを確認
**'メッセージ送信後、DM一覧画面に送信履歴が追加される'**というテストでは、
1.1回目のexpect(page).to have_content 'テストメッセージ'
⇨チャットルーム内の送信されたメッセージを確認
2.visit rooms_path
⇨DM一覧画面へ移動
3.2回目のexpect(page).to have_content 'テストメッセージ'
⇨DM一覧画面に最新のメッセージが表示されていることを確認
以上が個人側のDMのテストです。
続いて法人側のテストについて説明していきたいと思います。以下のコード例をご覧下さい。
####【法人側】 DM・通知のテスト
context '法人側:メッセージの受信〜送信(返信)' do
before do
# 個人側でメッセージ送信
visit company_path(company)
click_on 'DMを始める'
fill_in 'メッセージを入力して下さい', with: 'テストメッセージ'
click_button '送信'
logout(user)
# 法人ログイン
visit new_company_session_path
fill_in 'メールアドレス', with: company.email
fill_in 'パスワード', with: company.password
click_button 'ログイン'
expect(page).to have_content 'ログインしました。'
end
it 'ヘッダーに通知数が表示される' do
expect(page).to have_content '1 通知'
end
it 'リンクから通知一覧画面に遷移でき、個人ユーザーからのメッセージの受信が確認できる' do
click_on '通知'
expect(page).to have_content '通知'
expect(page).to have_content 'テスト 太郎さんからのメッセージがあります'
end
it 'リンクからチャットルームに入ることができ、受け取ったメッセージが表示される' do
click_on '通知'
click_on 'メッセージ'
expect expect(page).to have_content 'テストメッセージ'
end
it 'メッセージを送信できる' do
click_on '通知'
click_on 'メッセージ'
fill_in 'メッセージを入力して下さい', with: 'テストメッセージへの返信'
click_button '送信'
expect(page).to have_content 'テストメッセージへの返信'
end
end
こちらも特に難しいことはありません。
まず、beforeブロックで個人側がメッセージを送信し、その後法人がログインをしたというところまで記述します。
**'ヘッダーに通知数が表示される'**テストでは
expect(page).to have_content '1 通知'
⇨ログインすると、ヘッダーにメッセージ数に応じた通知数と、通知一覧へのリンクが表示されていることを確認します。
**'リンクから通知一覧画面に遷移でき、個人ユーザーからのメッセージの受信が確認できる'**テストでは
expect(page).to have_content 'テスト 太郎さんからのメッセージがあります'
⇨通知一覧画面へ遷移すると、誰からのメッセージなのかを確認できます。
あとはチャットルームへ入り、メッセージが来ているか確認、またこちらからもメッセージを送信(返信)できるかを確認するだけです。
以上、DM・通知のテストでした。
#終わりに
今回はシステムスペックによる記事の投稿、編集、DM、通知のテストについてまとめました。
2回に分けて書いたシステムスペック編もこれにて完結です!
自身の復習も兼ねて書いてきましたが、まだまだ改善点はあるなあと改めて思いました。
これを見た初学者の方が「なんだこれ、訳わからん!」とならないように、ちょっとずつ修正を加えていきたいと思います。
最初は分からない部分も多く苦労しましたが、それでも先日の初学者向けの勉強会RSpecビギナーズ!!に参加した時よりは自分でテストも書けるようになりましたし、何か聞かれたら少しは人に教えてあげられそうです。
このような機会を設けてくださった伊藤さん(@jnchito)をはじめ、お誘いしてくださった方々に改めて感謝致します。
また、稚拙ではありますがこの記事がRSpecビギナーズにとって少しでも役に立てばいいなと思っております。
最後までご覧頂きありがとうございました!
#参考記事
使えるRSpec入門・その4「どんなブラウザ操作も自由自在!逆引きCapybara大辞典」
【HTML】lavelタグにfor属性を付けるべき理由とは?使い方まで徹底解説!