[RSpec]system specでコントローラーのテスト(FactoryBot使用)

今回はRSpecの中トロ部分、system specでコントローラーのテストを実行していきます。

コントローラーのテストでは、実際に操作するような感覚でテストコードを書いていきます。

#開発環境

> ruby 2.6.3
> Rails 5.2.6

#前提

- FactoryBot使用
- Faker使用
- capybara使用
- devise使用

RSpecの準備、FactoryBotでのテストデータの定義等は、下記記事にまとめてますので、まだの方は先にこちらをどうぞ。
[[Rails]RSpecでテストを行う準備(FactoryBot使用)](https://qiita.com/nakachan1994/items/29369470d50d0fcd0f6c)

#ファイルの作成

まずは、spec配下にsystemフォルダを作って、その中に各コントローラーのファイルを作っていきます。

今回は、
posts_spec.rb
を例にコードを記述していきます。

作成した各モデルのファイルで、rails_helperの設定を読み込んでいきます。

rails_helperはRSpecの設定が記述してあるファイルです。

コードを１行書くだけで、読み込めるので先に記述しておきます。
最初にまとめて書いておくことで、書き忘れを防げます！

```rb:コントローラーファイル
require 'rails_helper'
```

#deviseのヘルパーを使えるようにする

controllerのテストを実行していく中で、sign_inなどのdeviseのヘルパーを使う機会が出てきます。

なぜRSpecのコードを書いていくときに、deviseのヘルパーが必要かというと、`アクセス権限でログインしてないユーザーを許可していない場合があるから`です。
ユーザーの編集ページはログインしてないときは、アクセスできないなど

`spec/rails_helper.rb`にdeviseのヘルパーを使えるようにコードを追加します。

```spec/rails_helper.rb
:
  # deviseのヘルパーメソッドをsystemで使用できるように設定
  config.include Devise::Test::IntegrationHelpers, type: :system
end
```

これでdeviseのヘルパーをsystemで使用できるようになりました。

#capybaraの設定

systemでCapybaraを使ってテストできるように、`spec_helper`に設定を追加します。

```spec/spec_helper.rb
:
RSpec.configure do |config|
  # 以下３行を追加
  config.before(:each, type: :system) do
    driven_by :rack_test
  end
:
```

これでsystemでcapybaraが使えるようになりました。

#テストコードを書いていく

posts_specでは投稿のテストと削除のテストを行います。

####テスト項目の洗い出し

まずはテスト項目の洗い出しを行っていきます。
投稿のテストでは、成功したときと失敗したときのテストを書いていきます。

```spec/system/posts_spec.rb
require 'rails_helper'

describe '投稿に関するテスト' do

  describe "投稿のテスト" do
    context "投稿が成功したとき" do
      it "投稿成功後の遷移先が正しいか" do
        # この中にテストの処理を書いていく
      end
      it "投稿成功後の表示の確認" do
        # この中にテストの処理を書いていく
      end
    end
    context "投稿が失敗したとき" do
      it "投稿失敗後の遷移先が正しいか" do
        # この中にテストの処理を書いていく
      end
      it "投稿失敗後の表示の確認" do
        # この中にテストの処理を書いていく
      end
    end
  end

  describe "削除のテスト" do
    it "削除リンクが表示されているか" do
      # この中にテストの処理を書いていく
    end
    it "削除されるか" do
      # この中にテストの処理を書いていく
    end
  end
end
```

RSpecの記述の基本形がわからない方はこちらの記事参照
[最低限覚えておきたいRSpecの基本構成](https://qiita.com/nakachan1994/items/02a2a08f4dd3c40ef121)

####テストデータをつくる

FactoryBotで作っておいたデータをつ使います。
わからない方は、こちらの記事で確認してください。
[[Rails]RSpecでテストを行う準備(FactoryBot使用)](https://qiita.com/nakachan1994/items/29369470d50d0fcd0f6c)

```spec/system/posts_spec.rb
:
describe '投稿に関するテスト' do
  # FactoryBotのテストデータを使う
  let!(:user) { create(:user) }
  let!(:post) { create(:post, user_id: user.id) }

  describe "投稿のテスト" do
:
```

####beforeを記述

投稿のテストも削除のテストもサインインしてから使う機能なので、サインインしてからテストを実行したいので、beforeでサインインしたあとに各ページに移動しておきます。

```spec/system/posts_spec.rb
:
describe "投稿のテスト" do
    # userでログインしてから新規投稿画面へ遷移
    before do
      sign_in user
      visit new_post_path
    end
:
describe "削除のテスト" do
    # userでログインしてから投稿一覧へ遷移
    before do
      sign_in user
      visit posts_path
    end
:
```

####テストの中身を記述

これで、準備ができたので、`it`の中でテストを実行していきます。

コード内に解説を入れています。

```spec/system/posts_spec.rb
require 'rails_helper'

describe '投稿に関するテスト' do
  # FactoryBotのテストデータを使う
  let!(:user) { create(:user) }
  let!(:post) { create(:post, user_id: user.id) }

  describe "投稿のテスト" do
    before do
      sign_in user
      visit new_post_path
    end

    context "投稿が成功したとき" do
      it "投稿成功後の遷移先が正しいか" do
        # contentのフォームにFakerでランダムな５文字の文字列を入れる
        fill_in 'post[content]', with: Faker::Lorem.characters(number: 5)
        # 投稿ボタンをクリック
        click_button "投稿"
        # 投稿成功後の遷移先が期待したパスになっているか
        expect(page).to have_current_path posts_path
      end
      it "投稿成功後の表示の確認" do
        # contentのフォームにFakerでランダムな５文字の文字列を入れる
        fill_in 'post[content]', with: Faker::Lorem.characters(number: 5)
        # 投稿ボタンをクリック
        click_button "投稿"
        # 投稿が成功して"投稿が成功しました"の文字があるか
        expect(page).to have_content "投稿に成功しました"
      end
    end
    
    context "投稿が失敗したとき" do
      it "投稿失敗後の遷移先が正しいか" do
        # contentのフォームを空にする
        fill_in 'post[content]', with: nil
        # 投稿ボタンをクリック
        click_button "投稿"
        # エラーが起きて選先が期待したパスになっているか
        expect(page).to have_current_path posts_path
      end
      it "投稿失敗後の表示の確認" do
        # contentのフォームを空にする
        fill_in 'post[content]', with: nil
        # 投稿ボタンをクリック
        click_button "投稿"
        # エラーが出てエラーメッセージが表示されているか
        expect(page).to have_content "入力してください"
      end
    end
  end
  
  describe "削除のテスト" do
    before do
      sign_in user
      visit posts_path
    end
    
    it "削除リンクが表示されているか" do
      # 削除のパスへのリンクがあるか
      expect(page).to have_link "", href: post_path(post)
    end
    it "削除されるか" do
      # 削除された時データベースから削除されているか
      expect { post.destroy }.to change(Post, :count).by(-1)
    end
  end
end
```

マッチャーについてわからない方はこちらの記事参照
[[Rails]RSpecでよく使うマッチャー10選](https://qiita.com/nakachan1994/items/e9938d70b3f6345368b2)

#まとめ

system specでコントローラーのテストを実施する手順は４つです。

>- system specファイルの作成
- deviseのヘルパーを使えるようにする
- capybaraの設定
- テストコードを書いていく

今回はかんたんな投稿と削除機能のみでしたが、今後も色んなテストコードを投稿していきたいと思います。