7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【第2回】RSpecビギナーが、ビギナーなりにSystemSpecを書いてみた(沼編)

Last updated at Posted at 2020-09-28

#はじめに

こちらは前回の記事【第1回】RSpecビギナーが、ビギナーなりにModelSpecを書いてみたの第2回、SystemSpec(システムスペック)編です。
ModelSpec(モデルスペック)について知りたい方は前回の記事へどうぞ!

また先日、RSpecの雄である伊藤淳一 @jnchito さんのご厚意で開催された初学者向けの勉強会(RSpecビギナーズ!!)にも参加致しましたので、こちらの動画も見て頂けるとより理解が深まると思うので、もしよろしければご覧下さい。

#この記事で扱うこと

  • SystemSpec(システムスペック)

    モデル、コントローラ、ビュー、全部テストできるよ!
  • システムスペックの具体的な記述例

    自身のポートフォリオを参考にして記述していきます。今回扱うのは主に個人会員と法人会員の認証のテストです。記事の投稿、編集、DM、通知のテストはこちら
    からどうぞ。

#この記事で扱わないこと

  • ModelSpec(モデルスペック)

    前回の記事をご覧下さい。
  • RSpecのセットアップ、準備

    (後述する参考書籍『EverydayRails-RSpecによるRailsテスト入門』にて詳しく記載してあるのでそちらを参考にしてください)

#前提

  • 対象

    RSpec書こうとしてるけど何が何だかさっぱりなんじゃあ〜:hugging:という初学者の方。
    ただ、伊藤さんの使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」
    こちらの記事内容をある程度は見ていたり、なんかやったことはあるな〜とか、最低限describe,it,expectの役割が分かる方が望ましいです。

  • 参考コード
    前述したように自身のポートフォリオを参考に記述するので、こちらにGitHubのリンクを貼っておきますが、テストの対象はサイトの基幹機能に絞っていますのでご了承ください。
    【サイトの基幹機能】(個人・法人会員の新規登録/ログイン/編集、法人会員登録の申請、記事の投稿/編集、DM、通知など)

  • テストを記述するための準備
    RSpecによるテストを記述するためには、gem 'rspec-rails'をはじめ、いくつかgemを入れたり設定をする必要があります。
    まずはテストを書く準備を整えてからお読みください。
    必要なものは『EverydayRails-RSpecによるRailsテスト入門』に記載してあります。
    というか、これを見ればこの記事を見なくても分かる人は分かると思います。
    具体例としてコードを見たいという方はそのままお読みいただけると嬉しいです。(本当に参考程度ですが)

#システムスペックについて
そもそもシステムスペックって何やねん?:thinking:って方もいるかと思います。

例えば皆さんがWebサイトを利用する場面を想像してください。
アカウント登録やログインをしたり、マイページを編集したり、何か投稿したり、誰かにDMを送ったり、、、挙げればきりがないんですが、実際のブラウザ上で何かしらのアクションを起こしますよね?
システムスペックは、この実際のブラウザ上でのユーザーのアクションが正しく動作するかということをテストします。
ユーザーが実際にWebサイトを見ながら動かすものが対象なので、そりゃ大事ですよね。
そのため前回記事の終わりに、モデルスペックを書くのも大事だけどシステムスペックを書くのが大事だと言及しました。

またRSpecの記事を探していると、**「フィーチャスペック」**というものも出てきますが、これはシステムスペックの前身だと思ってください。
RSpec 3.7から、Rails 5.1以降のアプリケーションに対してシステムスペック(system spec)をテストスイートに追加できるようになりましたので、バージョンが上記以降のものを使っている方はシステムスペックを書いていきましょう。

ちなみにですが、システムスペックを記述していくにもいろいろ設定が必要ですので、必要なgemや設定はEverydayRailsを参考にしてください。

#users_spec.rbのテスト

###①FactoryBotを使用し、userデータをあらかじめ用意しておく
前回、FactoryBotを使用し事前データを用意しました。システムスペックを書く際にも使用するので、参考までに以下に載せておきます。
FactoryBotの詳細は、前回の記事を参考にしてください。

①spec/factories/users.rb
FactoryBot.define do
  #FactoryBotを使用し、userデータをあらかじめ用意しておく
  factory :user do
    last_name { "テスト" }
    first_name { "太郎" }
    kana_last_name { "テスト" }
    kana_first_name { "タロウ" }
    email { "test@example.com" }
    postal_code { "1234567" }
    address { "東京都千代田区123-12-1" }
    phone_number { "12345678910" }
    password { "testtaro" }
  end
end

###②具体的なコードの記述
それではテストコードを書いていきます。
$ rails g rspec:system users を実行すると spec/system フォルダ内に users_spec.rb が作成されます。
テストコード(実際のブラウザ上の動きなど)はこちらのファイルに記述していきます。
以下、完成例です。

②spec/system/users_spec.rb
require 'rails_helper'

RSpec.describe 'User', type: :system do
  let(:user){FactoryBot.create(:user)}
  describe 'ユーザー認証のテスト' do
    describe 'ユーザー新規登録' do
      before do
        visit new_user_registration_path # 新規登録画面へ遷移
      end
      context '新規登録画面に遷移' do
        it '新規登録に成功する' do
          # fill_in で登録情報をテキストボックスへ入力
          fill_in '名前(姓)', with: "テスト"
          fill_in '名前(名)', with: "二郎"
          fill_in 'フリガナ(セイ)', with: "テスト"
          fill_in 'フリガナ(メイ)', with: "ジロウ"
          fill_in 'メールアドレス', with: "testjiro@example.com"
          fill_in '郵便番号(ハイフンなし)', with: "2222222"
          fill_in '住所', with: "東京都足立区123-12-1"
          fill_in '電話番号(ハイフンなし)', with: "22222222222"
          fill_in 'パスワード', with: "testjiro"
          fill_in '確認用パスワード', with: "testjiro"
          click_button '新規登録' # ボタンをクリック
          expect(page).to have_content 'アカウント登録が完了しました。'
        end
        
        it '新規登録に失敗する' do
          fill_in '名前(姓)', with: ""
          fill_in '名前(名)', with: ""
          fill_in 'フリガナ(セイ)', with: ""
          fill_in 'フリガナ(メイ)', with: ""
          fill_in 'メールアドレス', with: ""
          fill_in '郵便番号(ハイフンなし)', with: ""
          fill_in '住所', with: ""
          fill_in '電話番号(ハイフンなし)', with: ""
          fill_in 'パスワード', with: ""
          fill_in '確認用パスワード', with: ""
          click_button '新規登録'
          expect(page).to have_content '個人会員 は保存されませんでした。'
        end
      end
    end
    
    describe 'ユーザーログイン' do
      before do
        visit new_user_session_path
      end

      context 'ログイン画面に遷移' do
        it 'ログインに成功する' do
          fill_in "メールアドレス", with: user.email
          fill_in 'パスワード', with: user.password
          click_button 'ログイン'
          expect(page).to have_content 'ログインしました。'
        end

        it 'ログインに失敗する' do
          fill_in 'メールアドレス', with: ''
          fill_in 'パスワード', with: ''
          click_button 'ログイン'
          expect(current_path).to eq new_user_session_path
        end
      end
    end
  end

  describe 'ユーザーのテスト' do
    before do
      visit new_user_session_path
      fill_in 'メールアドレス', with: user.email
      fill_in 'パスワード', with: user.password
      click_button 'ログイン'
    end

    describe 'マイページのテスト' do
      it 'ヘッダーにマイページと表示される' do
        expect(page).to have_content('マイページ')
      end
      it 'マイページに遷移し編集リンクが表示される' do
        visit user_path(user)
        expect(page).to have_content('編集する')
      end
    end

    describe '編集のテスト' do
      context '編集画面へ遷移' do
        it '遷移ができる' do
          visit edit_user_path(user)
          expect(current_path).to eq edit_user_path(user)
        end
      end

      context '表示の確認と編集' do
        before do
          visit edit_user_path(user)
        end
        it '登録情報編集と表示される' do
          expect(page).to have_content('登録情報編集')
        end
        it '画像編集フォームが表示される' do
          expect(page).to have_field 'user[profile_image]'
        end
        it '名前編集フォームに自分の姓が表示される' do
          expect(page).to have_field '名前(姓)', with: user.last_name
        end
        it '名前編集フォームに自分の名が表示される' do
          expect(page).to have_field '名前(名)', with: user.first_name
        end
        it '名前編集フォームに自分のカナ姓が表示される' do
          expect(page).to have_field 'フリガナ(セイ)', with: user.kana_last_name
        end
        it '名前編集フォームに自分のカナ名が表示される' do
          expect(page).to have_field 'フリガナ(メイ)', with: user.kana_first_name
        end
        it 'メールアドレス編集フォームに自分のメールアドレスが表示される' do
          expect(page).to have_field 'メールアドレス', with: user.email
        end
        it '郵便番号編集フォームに自分の郵便番号が表示される' do
          expect(page).to have_field '郵便番号(ハイフンなし)', with: user.postal_code
        end
        it '住所編集フォームに自分の住所が表示される' do
          expect(page).to have_field '住所', with: user.address
        end
        it '電話番号編集フォームに自分の電話番号が表示される' do
          expect(page).to have_field '電話番号(ハイフンなし)', with: user.phone_number
        end
        it '自己紹介文編集フォームに自分の自己紹介文が表示される' do
          expect(page).to have_field '自己紹介文', with: user.introduction
        end
        it '編集に成功する' do
          # 名前を三郎に変更
          fill_in '名前(名)', with: '三郎'
          fill_in 'フリガナ(メイ)', with: 'サブロウ'
          click_button '変更を保存する'
          expect(page).to have_content '会員情報の更新が完了しました。'
          expect(page).to have_content 'テスト 三郎 (テスト サブロウ)'
          expect(current_path).to eq user_path(user)
        end
        it '編集に失敗する' do
          # first_name 名前(名)を空欄で入力
          fill_in '名前(名)', with: ''
          click_button '変更を保存する'
          expect(page).to have_content '件のエラーが発生したため 個人会員 は保存されませんでした。'
          expect(page).to have_content '名前(名)を入力してください'
          expect(current_path).to eq user_path(user)
        end
      end
    end
  end
  
end

「お、なんか FactoryBot の書き方が前回と違うぞ:thinking: let(:user) って何だ??」って思った方へ。

RSpecには let という機能があります。
これを使うとインスタンス変数をlet(:変数)という形に置き換え、後のコードで変数として使用することができます。
詳しくはこちらの記事を参考にしてください。RSpecのletを使うのはどんなときか?(翻訳)
beforeブロックで囲む場合と比べるとコード量も少なくなりますし、よりRSpecらしくなるので是非使ってみましょう。
ただ参考記事にも記述してある通り、letには**「遅延評価される」**という特徴があるので少し注意が必要です。こちらについては後のコード例で説明します。(私が沼にハマった原因です)


では、まずはユーザー認証のテストから説明していきます。 以下のコードをご覧下さい。
②spec/system/users_spec.rb
let(:user){FactoryBot.create(:user)}
  describe 'ユーザー認証のテスト' do
    describe 'ユーザー新規登録' do
      before do
        visit new_user_registration_path # 新規登録画面へ遷移
      end
      context '新規登録画面に遷移' do
        it '新規登録に成功する' do
          # fill_in で登録情報をテキストボックスへ入力
          fill_in '名前(姓)', with: "テスト"
          fill_in '名前(名)', with: "二郎"
          fill_in 'フリガナ(セイ)', with: "テスト"
          fill_in 'フリガナ(メイ)', with: "ジロウ"
          fill_in 'メールアドレス', with: "testjiro@example.com"
          fill_in '郵便番号(ハイフンなし)', with: "2222222"
          fill_in '住所', with: "東京都足立区123-12-1"
          fill_in '電話番号(ハイフンなし)', with: "22222222222"
          fill_in 'パスワード', with: "testjiro"
          fill_in '確認用パスワード', with: "testjiro"
          click_button '新規登録' # ボタンをクリック
          expect(page).to have_content 'アカウント登録が完了しました。'
        end

まず、beforeブロック内で visit new_user_registration_path という記述があります。
この visit というものは Capybara の機能で、 visit + path で特定のページに移動できます。
Capybaraとは、ユーザーが実際にWebサイトを使用しているかのように、様々なページを遷移しその際にどこか不具合がないか調べてくれる便利な機能です(カピバラがいろんなページを走り回って調べてくれているところを想像するとほっこりしますね)。

Capybaraを使うと visit以外にも様々な便利な機能が使えます。
Railsアプリケーションを作成した際に標準で gem 'capybara' がついているかと思いますが、確認してみてください。
参考記事:使えるRSpec入門・その4「どんなブラウザ操作も自由自在!逆引きCapybara大辞典」


さて、 ここからは実際の画面を想像しながら新規登録に成功するテストコードを記述していきます。
  • visit new_user_registration_path ⇨新規登録画面へ遷移しました。
  • fill_in '名前(姓)', with: "テスト"⇨テストという文字列を名前(姓)テキストボックスへ入力。以下の登録情報も同じく入力する。
  • click_button '新規登録'⇨ 新規登録ボタンを押下します。
  • expect(page).to have_content 'アカウント登録が完了しました。' ⇨既に使われているアドレスやパスワードを入力しない限り、アカウント登録が成功するので **アカウント登録が完了しました。**というメッセージが画面上に表示されるはずです。

    have_content はページ内に特定の文字列が表示されていることを確かめることができます。以降、have_◯◯という記述がたくさん出ますので、わからなければ都度上記の参考記事を確認してください。

はい、これで新規登録に成功するテストがシステムスペックで書けました! $ bundle exec rspec spec/system/users_spec.rb をターミナルで実行してみましょう! うまくできていればターミナルに *1 examples, 0 failures* という結果が出力されます。 エラーが出た場合は、エラー文を読み、コードで間違っている箇所がないか、もしくは設定の時点で何か漏れがないかなど確認しましょう。 また、慣れてきたら`expect(page).not_to have_content 'アカウント登録が完了しました。'`のように、expect(page) の後をnot_toに変更してみて**"テストがちゃんと失敗すること"**を再確認することも大事です。間違ったテストコードを記述してしまっている場合、わざとnot_toにしてあげているのにテストが成功してしまうことがあります。これを防ぐために敢えてnot_toなどに変更し、テストが失敗することを確認してあげましょう。

ログインやマイページ上での登録情報の編集なども、基本は同じなのでこちらを参考にしながら記述してみてください!

ちなみにclick_buttonは submitボタンやbuttonタグ の時に使います。
対してCSSによって見た目は同じように見えてもそれがリンクであるときはclick_linkを使います。
どちらでも使えるのが click_on になっています。じゃあ、全部click_onでよくね?と思ったそこのあなた。私も思いました:hugging: データを送信するためのボタンと遷移のためののリンクを明示的に分けてあげるためとか? そんな感じなのかなと自分で考えたりしたのですが、分けていることで他に何かメリットがあるのでしょうか。詳しい方教えて欲しいです。

#companies_spec.rbのテスト

###①FactoryBotを使用し、companyデータをあらかじめ用意しておく
前回作成したものです。

①spec/factories/companies.rb
FactoryBot.define do
  factory :company do
    company_name { "テスト株式会社" }
    kana_company_name { "テストカブシキガイシャ" }
    email { "testcompany@example.com" }
    postal_code { "1234567" }
    address { "東京都千代田区123-12-1" }
    phone_number { "12345678910" }
    password { "testcompany" }
    approved { true }
    is_active { true }
  end
end

###②具体的なコードの記述
法人の新規登録は個人会員の新規登録とは異なり、以下の流れになっています。

  1. 法人が新規登録のためにフォームを入力し、申請するボタンをクリックする。
  2. トップ画面へリダイレクトされ、「承認済メールが届くまで今しばらくお待ちください。」というメッセージが表示される。同時に管理者へ申請通知が送られる。
  3. 承認済メールが届くまでは法人ログインが制限される。
  4. 管理者が申請通知を確認し、申請ステータスを承認済に更新する。同時に法人の登録済アドレスへ承認済メールが送信される。
  5. ログインが可能になる。

####ベーコンレタスエッグつくねライスバーガーか!
取り乱しました。ややこしすぎる物事に対するくりぃむしちゅー上田さんのツッコミが出てしまいました。

基本は今までと同じなので慣れたらどうってことないのでしょうが、初学者にとっては難しく感じ苦労しました。
またこのような仕組みの参考記事が見つからなかったのが、今回の記事を書くきっかけにもなりました。(探し方が下手くそなだけかもしれませんが)
もし似たような仕組みを作っている初学者の方がいれば、こちらが参考になればと思います。

以下完成コード例です。

②spec/system/companies_spec.rb
require 'rails_helper'

RSpec.describe "Companies", type: :system do
  let!(:admin){FactoryBot.create(:admin)}
  let(:company){FactoryBot.create(:company)}
  describe 'ユーザー認証のテスト' do
    describe '法人の新規登録申請' do
      before do
        visit new_company_registration_path
      end
      it '登録申請に成功する' do
        fill_in '企業名', with: "テスト2株式会社"
        fill_in 'フリガナ', with: "テストツーカブシキガイシャ"
        fill_in 'メールアドレス', with: "test2company@example.com"
        fill_in '郵便番号(ハイフンなし)', with: "2222222"
        fill_in '住所', with: "東京都千代田区222-22-2"
        fill_in '電話番号(ハイフンなし)', with: "22222222222"
        fill_in 'パスワード', with: "test2company"
        fill_in '確認用パスワード', with: "test2company"
        click_button '申請する'
        expect(page).to have_content '登録申請ありがとうございます。法人会員専用ページは運営にて申請が承認がされた後に閲覧可能になります。承認済メールが届くまで今しばらくお待ちください。'
      end
      it '登録申請に失敗する' do
        fill_in '企業名', with: ""
        fill_in 'フリガナ', with: ""
        fill_in 'メールアドレス', with: ""
        fill_in '郵便番号(ハイフンなし)', with: ""
        fill_in '住所', with: ""
        fill_in '電話番号(ハイフンなし)', with: ""
        fill_in 'パスワード', with: ""
        fill_in '確認用パスワード', with: ""
        click_button '申請する'
        expect(page).to have_content "法人会員 は保存されませんでした。"
      end
    end
  end

  describe '法人がログイン可能になるまでのテスト' do
    before do
      # 法人が登録申請フォーム入力
      visit new_company_registration_path
      fill_in '企業名', with: "テスト2株式会社"
      fill_in 'フリガナ', with: "テストツーカブシキガイシャ"
      fill_in 'メールアドレス', with: "test2company@example.com"
      fill_in '郵便番号(ハイフンなし)', with: "2222222"
      fill_in '住所', with: "東京都千代田区222-22-2"
      fill_in '電話番号(ハイフンなし)', with: "22222222222"
      fill_in 'パスワード', with: "test2company"
      fill_in '確認用パスワード', with: "test2company"
      click_button '申請する' # 通知が送信される
    end
    
    describe '管理者:通知の確認〜申請承認のテスト' do
      before do
        # 管理者でログイン
        visit new_admin_session_path
        fill_in 'メールアドレス', with: admin.email
        fill_in 'パスワード', with: admin.password
        click_button 'ログイン'
      end
      it 'ヘッダーに法人登録申請と表示される' do
        expect(page).to have_content('法人登録申請')
      end
      it '法人登録申請一覧に申請してきた法人名が表示される' do
        visit admin_notifications_path
        expect(page).to have_content("テスト2株式会社 様からの法人登録申請があります")
      end
      it 'リンクから企業詳細ページへ遷移できる' do
        visit admin_notifications_path
        notification = Notification.find_by({receiver_id: admin.id, receiver_class: "admin", sender_id: Company.last.id, sender_class: "company"})
        within '.request-message' do
          click_link '法人登録申請' # ページに同一の文言のリンクがある場合(今回の場合「法人登録申請」)、classを指定してあげてwithin囲む
        end
        expect(current_path).to eq admin_company_path(notification.sender_id)
      end
      it '編集画面へ遷移する' do
        visit admin_company_path(Company.last.id)
        click_link '編集する'
        expect(current_path).to eq edit_admin_company_path(Company.last.id)
      end
      it '申請ステータスを承認済にする' do
        visit edit_admin_company_path(Company.last.id)
        choose "company_approved_true" # 申請ステータスを承認済にチェック(company_approved_trueはラジオボタン要素のid)
        click_button '変更を保存する'
        expect(page).to have_content '企業情報の更新が完了しました。'
        expect(current_path).to eq admin_company_path(Company.last.id)
      end
    end

    describe '法人:ログインのテスト' do
      context '承認前の法人ログイン' do
        it 'ログインに失敗し、メール受信後に再度ログインするようメッセージが出る' do
          visit new_company_session_path
          fill_in 'メールアドレス', with: "test2company@example.com"
          fill_in 'パスワード', with: "test2company"
          click_button 'ログイン'
          expect(page).to have_content '登録申請が未承認です。申し訳ございませんが、承認済メールが届くまで今しばらくお待ちください。'
        end
      end

      context '承認後の法人ログイン' do
        before do
          login_as(admin) # 管理者ログイン
          visit edit_admin_company_path(Company.last.id)
          choose "company_approved_true" # 申請ステータスを承認済にチェック(company_approved_trueはラジオボタン要素のid)
          click_button '変更を保存する'
          click_on 'ログアウト'
          visit new_company_session_path
        end
        it 'ログインに成功する' do
          fill_in 'メールアドレス', with: "test2company@example.com"
          fill_in 'パスワード', with: "test2company"
          click_button 'ログイン'
          expect(page).to have_content 'ログインしました。'
        end
        it 'ログインに失敗する' do
          fill_in 'メールアドレス', with: ""
          fill_in 'パスワード', with: ""
          click_button 'ログイン'
          expect(current_path).to eq new_company_session_path
        end
      end
    end
  end
    
  describe '法人会員のテスト' do
    before do
      visit new_company_session_path
      fill_in 'メールアドレス', with: company.email
      fill_in 'パスワード', with: company.password
      click_button 'ログイン'
    end

    describe 'マイページのテスト' do
      it 'ヘッダーにマイページと表示される' do
        expect(page).to have_content('マイページ')
      end
      it 'マイページに遷移し編集リンクが表示される' do
        visit corporate_company_path(company)
        expect(page).to have_content('編集する')
      end
    end

    describe '編集のテスト' do
      before do
        visit edit_corporate_company_path(company)
      end
      context '編集画面へ遷移の確認' do
        it '遷移ができる' do
          expect(current_path).to eq edit_corporate_company_path(company)
        end
      end
      context '表示及び編集の確認' do
        it '登録情報編集と表示される' do
          expect(page).to have_content('登録情報編集')
        end
        it 'プロフィール画像編集フォームが表示される' do
          expect(page).to have_field 'company[profile_image]'
        end
        it 'ヘッダー画像編集フォームが表示される' do
          expect(page).to have_field 'company[background_image]'
        end
        it '企業名編集フォームに企業名が表示される' do
          expect(page).to have_field '企業名', with: company.company_name
        end
        it 'フリガナ編集フォームに自分の企業カナ名が表示される' do
          expect(page).to have_field 'フリガナ', with: company.kana_company_name
        end
        it 'メールアドレス編集フォームに自分のメールアドレスが表示される' do
          expect(page).to have_field 'メールアドレス', with: company.email
        end
        it '郵便番号編集フォームに自分の郵便番号が表示される' do
          expect(page).to have_field '郵便番号(ハイフンなし)', with: company.postal_code
        end
        it '住所編集フォームに自分の住所が表示される' do
          expect(page).to have_field '住所', with: company.address
        end
        it '電話番号編集フォームに自分の電話番号が表示される' do
          expect(page).to have_field '電話番号(ハイフンなし)', with: company.phone_number
        end
        it '自己紹介文編集フォームに自分の自己紹介文が表示される' do
          expect(page).to have_field '自己紹介文', with: company.introduction
        end
        it '編集に成功する' do
          fill_in '自己紹介文', with: "テスト株式会社のマイページへようこそ!"
          click_button '変更を保存する'
          expect(page).to have_content '企業情報の更新が完了しました。'
          expect(current_path).to eq corporate_company_path(company)
        end
        it '編集に失敗する' do
          fill_in '企業名', with: ""
          click_button '変更を保存する'
          expect(page).to have_content '件のエラーが発生したため 法人会員 は保存されませんでした。'
        end
      end
    end
  end
end

まず、法人がログインできるようになるには管理者による申請の承認が必要なため、先に管理者を FactoryBot で作成します。$ bin/rails g factory_bot:model admin を実行し、必要なサンプルデータを入れましょう。以下、サンプルデータを入れたファイルです。
spec/factories/admins.rb
FactoryBot.define do
  factory :admin do
    email { "testadmin@example.com" }
    password { "testadmin"}
  end
end

法人の新規登録申請と、最後の登録情報編集のテストに関しては users_spec.rb のものとほぼ同じなので説明は省略します。


その間の**法人がログイン可能になるまでのテスト**を説明していきます。 以下のコードをご覧下さい。
②spec/system/companies_spec.rb
  describe '法人がログイン可能になるまでのテスト' do
    before do
      # 1.法人が登録申請フォーム入力
      visit new_company_registration_path
      fill_in '企業名', with: "テスト2株式会社"
      fill_in 'フリガナ', with: "テストツーカブシキガイシャ"
      fill_in 'メールアドレス', with: "test2company@example.com"
      fill_in '郵便番号(ハイフンなし)', with: "2222222"
      fill_in '住所', with: "東京都千代田区222-22-2"
      fill_in '電話番号(ハイフンなし)', with: "22222222222"
      fill_in 'パスワード', with: "test2company"
      fill_in '確認用パスワード', with: "test2company"
      click_button '申請する' # 通知が送信される
    end
    
    describe '管理者:通知の確認〜申請承認のテスト' do
      before do
        # 2.管理者でログイン
        visit new_admin_session_path
        fill_in 'メールアドレス', with: admin.email
        fill_in 'パスワード', with: admin.password
        click_button 'ログイン'
      end
      # 3.法人登録申請という文言があることから、管理者ログインができていることを確認
      it 'ヘッダーに法人登録申請と表示される' do
        expect(page).to have_content('法人登録申請')
      end
      # 4.法人登録申請のリンクをクリックし、申請企業名を確認
      it '法人登録申請一覧に申請してきた法人名が表示される' do
        visit admin_notifications_path
        expect(page).to have_content("テスト2株式会社 様からの法人登録申請があります")
      end
      # 5.申請一覧から法人登録申請というリンクをクリックする
      it 'リンクから企業詳細ページへ遷移できる' do
        visit admin_notifications_path
        notification = Notification.find_by({receiver_id: admin.id, receiver_class: "admin", sender_id: Company.last.id, sender_class: "company"})
        within '.request-message' do
          click_link '法人登録申請' # ページに同一の文言のリンクがある場合(今回の場合「法人登録申請」)、classを指定してあげてwithin囲む
        end
        expect(current_path).to eq admin_company_path(notification.sender_id)
      end
      # 6.登録情報編集ページへ
      it '編集画面へ遷移する' do
        visit admin_company_path(Company.last.id)
        click_link '編集する'
        expect(current_path).to eq edit_admin_company_path(Company.last.id)
      end
      # 7.ラジオボタンに注意
      it '申請ステータスを承認済にする' do
        visit edit_admin_company_path(Company.last.id)
        choose "company_approved_true" # 申請ステータスを承認済にチェック(company_approved_trueはラジオボタン要素のid)
        click_button '変更を保存する'
        expect(page).to have_content '企業情報の更新が完了しました。'
        expect(current_path).to eq admin_company_path(Company.last.id)
      end
    end

    describe '法人:ログインのテスト' do
      context '承認前の法人ログイン' do
        it 'ログインに失敗し、メール受信後に再度ログインするようメッセージが出る' do
          visit new_company_session_path
          fill_in 'メールアドレス', with: "test2company@example.com"
          fill_in 'パスワード', with: "test2company"
          click_button 'ログイン'
          expect(page).to have_content '登録申請が未承認です。申し訳ございませんが、承認済メールが届くまで今しばらくお待ちください。'
        end
      end

      context '承認後の法人ログイン' do
        before do
          login_as(admin) # 管理者ログイン
          visit edit_admin_company_path(Company.last.id)
          choose "company_approved_true" # 申請ステータスを承認済にチェック(company_approved_trueはラジオボタン要素のid)
          click_button '変更を保存する'
          click_on 'ログアウト'
          visit new_company_session_path
        end
        it 'ログインに成功する' do
          fill_in 'メールアドレス', with: "test2company@example.com"
          fill_in 'パスワード', with: "test2company"
          click_button 'ログイン'
          expect(page).to have_content 'ログインしました。'
        end
        it 'ログインに失敗する' do
          fill_in 'メールアドレス', with: ""
          fill_in 'パスワード', with: ""
          click_button 'ログイン'
          expect(current_path).to eq new_company_session_path
        end
      end
    end
  end

私「よし、ファイルも作成されたし **let(:admin){FactoryBot.create(:admin)}** と書いてっと...これでadminという変数が使えるようになったぜ!」

一見問題ないように見える上記の記述、これが沼への入り口だったのです。

  1. 法人が登録申請フォームを入力
    beforeブロックでは法人が登録申請フォームを入力し、申請するボタンを押したところまでを記述します。
    この時点で管理者へ申請通知が送信されている状態です。

  2. つまづきポイント:hugging: 管理者でログイン「letの遅延評価」
    私「よし、申請承認するためにまずは管理者でログインっと...あれ?なんかめちゃくちゃテスト失敗するんですが、なぜ...??」
    私はここでテストが失敗する理由をしばらく見つけることができず、負のスパイラルへ陥りました。
    よく調べてみると先ほどの let(:admin){FactoryBot.create(:admin)} という記述、注目すべきは**let(:admin)の部分。一見何も問題がないように見えますが、先にちょこっと話していた通り、letには「遅延評価される」という特徴があるのです。
    遅延評価って何やねん?:thinking:という方はこちらの記事をどうぞ。
    RSpecのletでcreateした時に気をつけること 〜遅延評価〜
    RSpecのletを使うのはどんなときか?(翻訳)
    こちらの記事によると、ただのletでcreateした時は、代入した変数が参照されたタイミングで評価されるんだそう=遅延評価。
    つまり、fill_in 'メールアドレス', with: admin.emailのように admin という変数が使用されたタイミングでなければ、adminのデータ内容というのは無いものとみなされます。なるほど、でもadminって書いてるんだよな〜と思いながら
    let!**にしてみると無事テストが通りました。
    うむ、、通ったけどなぜだろう... describeがいくつか重なっていることが原因かなとも思いましたが、結局わかりませんでした。。詳しい方、教えてください。。

  3. 法人登録申請という文言があることから、管理者ログインができていることを確認
    管理者ログイン後のヘッダーには法人登録申請というリンクがあるため、その表示があることを確かめ、ログインができたことを確認します。

  4. 法人登録申請のリンクをクリックし、申請企業名を確認
    ヘッダーのリンクをクリックすると、企業名が表示されるので 1で申請した企業であるかを確認

  5. つまづきポイント:hugging:「申請一覧から法人登録申請というリンクをクリックする」
    ここでもつまづきました。注目すべきはwithin '.request-message' doで囲んでいる部分。今やりたいことは「法人登録申請というリンクをクリックし、企業詳細ページへ遷移する」ということです。普通であればclick_linkかclick_onで遷移できるはずです。しかし上手くいかずエラーが出ました。この時のエラー文がこちら。

Capybara::Ambiguous: Ambiguous match, found 2 elements matching visible link "法人登録申請"

これを要約すると
カピバラ「あの、"法人登録申請"というリンクが2つあるんですが、僕はどっちに行けばいいですか??」
という意味になります。私はここで気づきました。

法人登録申請
2つあるやん...カピバラごめん...
ということで改善法をググった結果、click_linkではなく、ビューの対象部分に固有のid名(request_message)を指定し、テストコードはfind("#request_message").clickという記述で解決しました。idを指定して、対象の方をクリックしてねということですね。
2020.10.01追記 コメントでアドバイスいただいた通り、withinで解決しました。こちらの方が、ブラウザでの見た目そのままクリックする形なのでより良いですね!

②spec/system/companies_spec.rb
within '.request-message' do
  click_link '法人登録申請' # ページに同一の文言のリンクがある場合(今回の場合「法人登録申請」)、classを指定してあげてwithinで囲む
end
<td class="text-center request-message"> # ビューの対象部分にrequest-messageというクラスを指定。こちらをターゲットにする
  <strong><%= notification.sender_name %></strong> 様からの<%= link_to '法人登録申請', admin_company_path(notification.sender_id) %>があります。登録内容を確認してください。
  <%= " (#{time_ago_in_words(notification.created_at)}前)" %>
</td>

6.登録情報編集ページへ
ここは簡単ですね。特に説明はしません。

7.つまづきポイント:hugging:「ラジオボタンに注意」
Capybara の choose という機能を使い、申請ステータスを承認済にチェックします。
choose "company_approved_true"という形で記述します。 "company_approved_true" ってどこからきたかというと、googleの検証ツールを使うと、inputタグの中にid名が割り振られていることが分かります。ラジオボタンはそのidを指定して選択しているという形になっています。ただ、他の記事を見た際にlabelタグ内の文字列をターゲットに選択している記事もあります。今回の場合であれば、"承認済"という文字列です。ブラウザ上で見る際は、普通であれば文字列を見て選択すると思います。その為、「検証ツールでidを指定して選択」という方法はできるだけしない方が良いのかもしれません。私は文字列を指定するとテストが通らなかった為、このような形にしています。こちらも詳しい方がいらっしゃいましたら、教えていただきたいです。エラーは解決できても、"それがなぜなのか"という部分が解決できていないのでこの記事を見ていらっしゃる方には申し訳ないです。。逆にわかったら教えてください:bow_tone1:

法人登録申請

はい!ということで管理者による通知の確認〜申請承認のテストはこれで終わりです!

最後に承認前と承認後でログイン制限がされているか否かを確認するテストを書きます。
ここまで見てくださった方は、恐らく分かると思いますのでコードを参考にしながら記述してみてください!

ポイントとして、承認後の法人ログインのテストにあるbeforeブロック内のlogin_as(admin)という記述だけチラッと説明します!
やっていることは単純に管理者でログインしているだけなのですが、"login_as"という機能は前回の記事でお話しした "deviseのヘルパーメソッドをRSpec内で使用可能にする設定" がされていないと、使うことができませんのでここだけ注意です!
何だっけ?と思った方は、前回の記事【第1回】RSpecビギナーが、ビギナーなりにModelSpecを書いてみたをご覧下さい。

#終わりに
今回は主にシステムスペックによる個人会員と法人会員の認証のテストについてまとめました。
記事の投稿、編集、DM、通知のテストも書こうと思ってたのですが、想像よりも長くなってしまったので一旦ここで分けます。
こちらの機能のシステムスペックに関してはこちらからどうぞ。
【第3回】RSpecビギナーが、ビギナーなりにSystemSpecを書いてみた(完結編)

最後までご覧頂きありがとうございました!

2020.10.01追記
コメントでアドバイスいただいた通り、fill_inなどの部分を修正しております。
その際にlabelタグが正しい形で書かれていなければ上手く動かなかったため注意です。書き方はコメント欄を参考にしてください。

#参考記事
使えるRSpec入門・その4「どんなブラウザ操作も自由自在!逆引きCapybara大辞典」
RSpecのletでcreateした時に気をつけること 〜遅延評価〜
RSpecのletを使うのはどんなときか?(翻訳)

7
6
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?