Rspecまとめ

すぐ忘れるので、まとめてみた

## Rspecを導入する
```ruby:Gemfile
group :development, :test do
  gem "rspec-rails"
end
```
```ruby:ターミナル
rails g rspec:install
```
```ruby:.rspec
# 見やすくするため追加
--format documentation
```
## FactoryBotを導入する
```ruby:Gemfile
group :development, :test do
  gem "factory_bot_rails"
end
```

##Capybaraを導入する
```ruby:Gemfile
  gem 'capybara'
```

## Rspecを実行する
### rails_helperの設定
```ruby:spec/rails_helper
require 'capybara/rspec'

RSpec.configure do |config|
  config.include Devise::Test::IntegrationHelpers, type: :request    #Deviseのテスト
  config.include FactoryBot::Syntax::Methods    #FactoryBotの利用
end
```
### requrest_spec
#### requestの導入
```ruby:ターミナル
# コマンドを打たずに、ファイルを手動で作成しても良い
rails g rspec:request user
```

```ruby:top_spec.rb
require 'rails_helper'

RSpec.describe "Tops", type: :request do
  describe "GET /index" do

    before do
      get root_path
    end

    example "topページへアクセスできること"  do
      expect(response).to have_http_status(:success)
    end

    example "ログイン機能が含まれること" do
      expect(response.body).to include("ログイン")
    end
  end

end
```
#### Deviseのspec
```ruby:users_controller.rb
class UsersController < ApplicationController
  before_action :authenticate_user!

  def index
    @user = User.find(current_user.id)
  end
end
```
```ruby:spec/factories/user.rb
FactoryBot.define do
  factory :testuser, class: 'User' do
    name { "testuser" }
    sequence(:email) { |n| "testuser#{n}@user.com" }
    password { "foobar" }
  end
end
```
```ruby:users_spec.rb
require 'rails_helper'

RSpec.describe "Users", type: :request do
 let(:user) { create(:testuser) }

  describe "GET /index" do
    context "ログインしていない場合" do
      example "ログインページへリダイレクトすること" do
        get users_path
        expect(response).to redirect_to user_session_path
      end
    end

    context "ログインしている場合" do
      before do
        sign_in user
        get users_path
      end

      example "indexページが表示できること" do
        expect(response).to have_http_status(:success)
      end

      example "ユーザー情報が含まれること" do
        expect(response.body).to include(user.name)
        expect(response.body).to include(user.email)
      end
    end
  end
```
#### ransackのspec
```ruby:institutions_controller.rb
class InstitutionsController < ApplicationController
  before_action :set_q

  def index
    max_num = 50
    if @q.result.count > 50
      @institution = @q.result.limit(max_num)
    else
      @institution = @q.result.all
    end
  end

  private

  def set_q
    @q = Institution.ransack(params[:q])
  end
end
```
```erb:app/views/institution/index.html.erb
<div class="institution_index_main">
  <h3>医療機関検索フォーム</h1>
  <%= search_form_for @q, url: institutions_path do |f| %>
    <div class="flex_center">
      <div class="search_form_container">
        <div class="addres_cont_container">
          <%= f.label :address_cont, '住所で検索' %>
          <%= f.search_field :address_cont %>
        </div>
        <div class="name_introduction_cont_container">
          <%= f.label :name_or_introduction_cont, '情報で検索' %>
          <%= f.search_field :name_or_introduction_cont %>
        </div>
      </div>
      <div class="search_submit_container">
        <%= f.submit '検　索', class:"search_submit", id: "institution_search_submit" %>
      </div>
    </div>
  <% end %>
</div>
```
```ruby:factories/institution.rb
FactoryBot.define do
  factory :testinstitution, class: "Institution" do
    name { "testinstitution" }
    postcode { 1234567 }
    address { "東京都東京区東京1-2-3東京ビル1F" }
  end
end
```
```ruby:institution_spec.rb
# request_specの場合

require 'rails_helper'

RSpec.describe "Institutions", type: :request do
  let!(:institution) { create(:testinstitution) }

  describe "GET /index" do
    before do
      get institutions_path, params: { q: { name_cont: "test"} }
    end

    example "ページを表示できること" do
      expect(response).to have_http_status(:success)
    end

    example "医療機関情報が含まれること" do
      expect(response.body).to include(institution.name)
      expect(response.body).to include(institution.address)
    end
  end
end
```
```ruby:spec/features/institution_feature_spec.rb
# feature_specの場合

require 'rails_helper'

RSpec.feature "Institution_Features", type: :feature do
  let!(:institution) { create(:testinstitution) }

  feature "医療機関検索" do
    before do
      visit institutions_path
    end

    scenario "住所に検索語を含むとき" do
      fill_in 'q[address_cont]', with: '東京都'
      find('#institution_search_submit').click
      expect(page.status_code).to eq(200)
      expect(page).to have_content(institution.name)
      expect(page).to have_content(institution.address)
    end

    scenario "住所に検索語を含まないとき" do
      fill_in 'q[address_cont]', with: '大阪府'
      find('#institution_search_submit').click
      expect(page.status_code).to eq(200)
      expect(page).not_to have_content(institution.name)
      expect(page).not_to have_content(institution.address)
    end

    scenario "検索欄が空欄のとき" do
      fill_in 'q[address_cont]', with: ''
      find('#institution_search_submit').click
      expect(page.status_code).to eq(200)
      expect(page).to have_content(institution.name)
      expect(page).to have_content(institution.address)
    end

    scenario "名前に検索語を含むとき" do
      fill_in 'q[name_or_introduction_cont]', with: 'test'
      find('#institution_search_submit').click
      expect(page.status_code).to eq(200)
      expect(page).to have_content(institution.name)
      expect(page).to have_content(institution.address)
    end

    scenario "名前に検索語を含まないとき" do
      fill_in 'q[name_or_introduction_cont]', with: 'example'
      find('#institution_search_submit').click
      expect(page.status_code).to eq(200)
      expect(page).not_to have_content(institution.name)
      expect(page).not_to have_content(institution.address)
    end

    scenario "検索欄が空欄の時" do
      fill_in 'q[name_or_introduction_cont]', with: ''
      find('#institution_search_submit').click
      expect(page.status_code).to eq(200)
      expect(page).to have_content(institution.name)
      expect(page).to have_content(institution.address)
    end
  end
end
```
#### ストロングパラメータのspec
```ruby:users_controller.rb
class UsersController < ApplicationController
  before_action :authenticate_user!

  def update
    @user = User.find(current_user.id)
    if @user.update(user_params)
      redirect_to edit_user_path(current_user), notice: "プロフィール情報を更新しました"
    else
      render "edit"
    end
  end

  private
  def user_params
    params.require(:user).permit(:name, :image)
  end
end
```
```ruby:requests/user_spec.rb
require 'rails_helper'

RSpec.describe "Users", type: :request do
  let(:user) { create(:testuser) }

  describe 'POST #create' do
    before do
      sign_in user
    end

    example 'リクエストが成功すること' do
      put user_path(user.id), params: { user: { name: "exampleuser", image: "image.png" } }
      expect(response.status).to eq 302
    end

    example 'ユーザー名が更新されること' do
      expect do
        put user_path(user.id), params: { user: { name: "exampleuser", image: "image.png" } }
      end.to change { User.find(user.id).name }.from('testuser').to('exampleuser')
    end
  end
end
```
#### POST PUT DELETEメソッドのspec
```ruby:requests/institution_spec.rb
require 'rails_helper'

RSpec.describe "Institutions", type: :request do
  let!(:institution) { create(:testinstitution) }
  let(:param) do
    {
      institution: {
        name: 'examplehospital',
        postcode: 9876543,
        prefecture: "福岡県",
        address_city: "福岡市福岡",
        address_street: "4-5-6",
        address_building: "福岡ショッピング5F",
        address: "福岡県福岡市福岡4-5-6福岡ショッピング5F",
        introduction: "整形外科、リハビリテーション科",
        image: "examplehospital.png"
      }
    }
  end

  describe 'POST /create' do
    example 'リクエストが成功すること' do
      post institutions_path, params: param
      expect(response.status).to eq 302
    end

    example '医療機関が登録されること' do
      expect do
        post institutions_path, params: param
      end.to change(Institution, :count).by(1)
    end
  end

  describe 'PUT /update' do
    example 'リクエストが成功すること' do
      put institution_path(institution.id), params: param
      expect(response.status).to eq 302
    end

    example '医療機関情報が更新されること' do
      expect do
        put institution_path(institution.id), params: param
      end.to change { Institution.find(institution.id).name }.from('testinstitution').to('examplehospital')
    end
  end

  describe 'DELETE /destroy' do
    it 'リクエストが成功すること' do
      delete institution_path(institution.id), params: { id: institution.id }
      expect(response.status).to eq 302
    end

    it '医療機関情報が削除されること' do
      expect do
        delete institution_path(institution.id), params: { id: institution.id }
      end.to change(Institution, :count).by(-1)
    end

    it '医療機関一覧にリダイレクトすること' do
      delete institution_path(institution.id), params: { id: institution.id }
      expect(response).to redirect_to(institutions_path)
    end
  end
end
```
### feature_spec
```ruby:features_user_spec.rb
require 'rails_helper'

RSpec.feature 'Users_Features', type: :feature do
  include Devise::Test::IntegrationHelpers

  describe "user_login" do
    let(:user) { create(:testuser)}

    scenario "新規作成できるか" do
      visit new_user_registration_path
      expect(page).to have_content("新規登録")
      fill_in "user_registration_email", with: "example@example.com"
      fill_in "user_registration_password", with: "foobar"
      fill_in "user_registration_confirm_password", with: "foobar"
      expect do
        click_button "新規登録"
      end.to change(User, :count).by(1)
    end
  end

  describe "link_to_users" do
    let(:user) { create(:testuser) }

    background do
      sign_in user
    end

    scenario 'プロフィール画面からプロフィール編集画面へ遷移できるか' do
      visit users_path
      expect(page).to have_content(user.name)
      expect(page).to have_content(user.email)
      expect(page).to have_content("アカウント情報を編集する")
      expect(page).to have_content("プロフィールを編集する")
      click_link "プロフィールを編集する"
      expect(page).to have_content("ユーザープロフィール編集")
    end
  end
end
```
```ruby:model/consultation.rb
class Consultationhour < ApplicationRecord
  belongs_to :institution
  validates :start_time, presence: true, format: { with: /[[0-2]0-9]:[0-5][0-9]/, message: "は[00:00]の形式で入力してください" }
  validates :end_time, presence: true, format: { with: /[[0-2]0-9]:[0-5][0-9]/, message: "は[00:00]の形式で入力してください" }
  validate :before_start_time

  def before_start_time
    if start_time != nil && end_time != nil
      if end_time < start_time
        errors.add(:end_time, "は開始時間よりも後に設定してください")
      end
    end
  end
end
```
```ruby:consultation_feature_spec.rb
require 'rails_helper'

RSpec.feature "Consultationhour_Features", type: :feature do
  let(:medicalstaff) { create(:teststaff) }
  let(:institution) { create(:testinstitution) }

  before do
    login_staff(medicalstaff)
  end

  feature "登録内容が正しいとき" do
    before do
      visit institution_path(institution.id)
      click_link "診療時間を登録する"
    end

    scenario "新規作成できるか" do
      fill_in "new_start_time", with: "08:00"
      fill_in "new_end_time", with: "13:00"
      find('.monday_container').all('option')[1].select_option
      find('.tuesday_container').all('option')[1].select_option
      find('.wednesday_container').all('option')[0].select_option
      find('.thursday_container').all('option')[1].select_option
      find('.friday_container').all('option')[1].select_option
      find('.saturday_container').all('option')[1].select_option
      find('.sunday_container').all('option')[2].select_option
      find('.holiday_container').all('option')[1].select_option
      fill_in "new_detail", with: "日曜日は非常勤医師"
      expect do
        click_button "登録する"
      end.to change(Consultationhour, :count).by(1)
      expect(page).to have_content("診療時間を追加しました")
      expect(page).to have_content("日曜日は非常勤医師")
    end
  end

  feature "登録内容が正しくないとき" do
    before do
      visit institution_path(institution.id)
      click_link "診療時間を登録する"
    end

    scenario "未入力エラーメッセージが表示されるか" do
      click_button "登録する"
      expect(page).to have_content("開始時間が入力されていません")
      expect(page).to have_content("開始時間は[00:00]の形式で入力してください")
      expect(page).to have_content("終了時間が入力されていません")
      expect(page).to have_content("終了時間は[00:00]の形式で入力してください")
    end

    scenario "開始時間と終了時間のエラーメッセージが表示されるか" do
      fill_in "new_start_time", with: "13:00"
      fill_in "new_end_time", with: "10:00"
      click_button "登録する"
      expect(page).to have_content("終了時間は開始時間よりも後に設定してください")
    end
  end
end
```
#### 中間テーブルのテスト
```ruby:spec/factories/favorite.rb
FactoryBot.define do
  factory :testfavorite, class: "Favorite" do
    user { create(:testuser) }
    institution { create(:institution) }
  end
end
```
```ruby:spec/features/user_feature_spec.rb
describe "link_to_users" do
    let!(:user) { create(:testuser) }
    let!(:institution) { create(:testinstitution) }
    let!(:favorite) { create(:testfavorite, user: user, institution: institution) }

    background do
      sign_in user
    end

    scenario 'プロフィール画面からアカウント情報編集画面へ遷移し、プロフィール画面へ戻れるか', js: true do
      visit users_path
      expect(page).to have_content("お気に入り解除")
      find('.like-btn').click
      expect(page).not_to have_content("お気に入り解除")
    end
```
##### 別）user作成時にfavoriteを作成する方法
```ruby:spec/factories/user.rb
FactoryBot.define do
  factory :testuser, class: 'User' do
    name { "testuser" }
    sequence(:email) { |n| "testuser#{n}@user.com" }
    password { "foobar" }

    after(:create) do |user|
      create(:testfavorite, user: user, institution: create(:testinstitution) )
    end
  end
end

# traitを使う方法
FactoryBot.define do
  factory :testuser, class: 'User' do
    name { "testuser" }
    sequence(:email) { |n| "testuser#{n}@user.com" }
    password { "foobar" }

    trait :user_favorite do
      after(:create) do |user|
        create(:testfavorite, user: user, institution: create(:testinstitution) )
      end
    end
  end
end

let(:user) { create(:testuser, :user_favorite) }  # favoriteを作る
let(:user) { create(:testuser) }  # favoriteを作らない
```

#### Javascriptのリンクをテスト
```ruby:spec/rails_helper.rb
Capybara.javascript_driver = :selenium_chrome_headless  # 追加
```
```ruby:factories/institution.rb
FactoryBot.define do
  factory :testinstitution, class: "Institution" do
    name { "testinstitution" }
    postcode { 1234567 }
    prefecture { "東京都" }
    address_city { "東京区東京" }
    address_street { "1-2-3" }
    address_building { "東京ビル1F" }
    address { "東京都東京区東京1-2-3東京ビル1F" }
  end
end
```
```ruby:features/insitutiton_feature_spec.rb
require 'rails_helper'

RSpec.feature "Institution_Features", type: :feature do
  let!(:institution) { create(:testinstitution) }

  scenario "詳細ページへ遷移できるか", js: true do
    visit institutions_path
    find(".institution_index_tbody_tr").click
    expect(page).to have_content("医療機関情報")
    expect(page).to have_content("編　集")
    expect(page).to have_content("削　除")
  end

  scenario "医療機関情報を編集できるか" do
    visit edit_institution_path(institution.id)
    expect(page).to have_content("医療機関情報修正")
    fill_in "institution_name_text", with: "testclinic"
    click_button "更新する"
    expect(page).to have_content("testclinic")
    expect(page).not_to have_content(institution.name)
  end
end
```
### ヘルパーを導入する
```ruby:spec/support/LoginMacro.rb
module LoginMacro
  def login_staff(staff)
    visit new_medicalstaff_session_path
      fill_in 'medicalstaff_login_email', with: staff.email
      fill_in 'medicalstaff_login_password', with: staff.password
      click_on 'ログイン'
  end
end
```
```ruby:spec/rails_helper.rb
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f } 
  # 有効化

RSpec.configure do |config|
  config.include LoginMacro    # 追加
  config.include Devise::Test::IntegrationHelpers, type: :request
  config.include FactoryBot::Syntax::Methods
  Capybara.javascript_driver = :selenium_chrome_headless
end
```
```ruby:spec/features/institution_feature_spec.rb
RSpec.feature "Institution_Features", type: :feature do
  let!(:institution) { create(:testinstitution) }

  feature "medicalstaffでログインしたとき" do
    let(:medicalstaff) { create(:teststaff) }
    before do
      login_staff(medicalstaff)
    end
    scenario "新規作成画面へ遷移できるか" do
      visit institutions_path
      expect(page).to have_content("医療機関検索フォーム")
      expect(page).to have_content(institution.name)
      click_link "新規作成"
      expect(page).to have_content("医療機関情報新規作成")
    end
  end
end
```
## 参考資料
### Rspecを導入する
[RSpecには表示の出力をキレイにする --format documentation というオプションがある](https://shinkufencer.hateblo.jp/entry/2019/01/21/233000)

### FactoryBotを導入する
[【Rails】RSpecとFactroyBotの導入・設定まとめ](https://nyoken.com/rspec-setup)
[FactoryBotを使う時に覚えておきたい、たった5つのこと](https://qiita.com/piggydev/items/32717b6c382272e2134e)
[RSpec - FactoryBotを導入し、効率良く意味のあるデータを作成する](https://forest-valley17.hatenablog.com/entry/2018/09/28/075644)

### Capybaraを導入する
[Capybaraチートシート](https://qiita.com/morrr/items/0e24251c049180218db4)

### Rspecを実行する
[使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」](https://qiita.com/jnchito/items/42193d066bd61c740612)
[使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」](https://qiita.com/jnchito/items/2e79a1abe7cd8214caa5)
[Rspecでbindingを使って変数の中身を調べる方法](https://qiita.com/ngron/items/712bf3e06b7566e492ee)
[現場で使えるRSpecパターン集 for Rails App](https://www.mogulla3.tech/entry/2019/08/29/093047)
[今日から使える！RSpec 3で追加された8つの新機能](https://qiita.com/jnchito/items/a4a51852c2c678b57868)

#### requrest_spec
[Rails RSpecの基本 ~Controller編~](https://qiita.com/shizuma/items/84e07e558abd6593df15)
[Rails5でコントローラのテストをController specからRequest specに移行する](https://qiita.com/t2kojima/items/ad7a8ade9e7a99fb4384)

#### Deviseのspec
[[Rails]Request Specでのログインの実施](https://qiita.com/iwkmsy9618/items/d097f0f315b93049c6c5)
[RSpec初心者のdevise認証システムテスト](https://qiita.com/zongxiaojie/items/d488edd42ba3864859c7)
[Request SpecでDeviseのsing_in userを実施する](https://qiita.com/Hyuga-Tsukui/items/57fa77df2b2942414307)

#### ransackのspec
[ruby-on-rails - Rspecテストのランサックは常にすべてのインスタンスを返す](https://base64.work/so/ruby-on-rails/2627769)
[【Rails6】RSpecによる検索機能（ransack）の結合テストの実装](https://qiita.com/narimiya/items/13c7a9c3fe28e72ebaaa)

#### ストロングパラメータのspec
[Request Specでテストを書くときはStrong Parametersを通る前のパラメーターも意識しましょう](https://qiita.com/akagire/items/b6d51ba2a49e0117d217)

#### POST PUT DELETEメソッドのspec
[Rails5でコントローラのテストをController specからRequest specに移行する](https://qiita.com/t2kojima/items/ad7a8ade9e7a99fb4384)


#### feature_spec
[[Rails, devise, RSpec] テストでサインインする](https://qiita.com/sijiaoh/items/3362377c8aa61bf6cdfe)
[使えるRSpec入門・その4「どんなブラウザ操作も自由自在！逆引きCapybara大辞典」](https://qiita.com/jnchito/items/607f956263c38a5fec24)
[【RSpec/capybara】fill_inが上手く動作しない](https://qiita.com/Takara1356/items/cc8535c4a6e66c5977e2)
[[Rails][RSpec] Capybaraでフォーム入力をシミュレートしてテストする](https://qiita.com/kidach1/items/b0e607c83e9da9380d7e)
[【Capybara】idもnameもlabelも存在しないセレクトボックスのオプションを選択する](https://qiita.com/mi0/items/47236aafab7f28e8227c)

#### 中間テーブルのテスト
[rspecのテスト環境で多対多のアソシエーションを一括生成する方法 +α](https://qiita.com/Kohei_Kishimoto0214/items/425ecb56e8586e46354f)
[コントローラー単体テストエラーを解決したいです。](https://teratail.com/questions/236680)
[FactoryBotで中間テーブルのテストデータを用意する方法](https://t1gerk1ngd0m.hatenablog.com/entry/2019/08/24/233047)

#### Javascriptのリンクをテスト
[【Rails】JavaScriptが絡むテスト](https://qiita.com/koki_73/items/ffc115ed542203161cef)

#### ヘルパーを導入する
[サポートモジュールを使って結合テストの可読性を向上させる方法](https://qiita.com/narimiya/items/b897db14551191e41fb2)