RSpec初心者のdevise認証システムテスト

##はじめに
RSpecの機能や構文を全く知らない状態からユーザー登録や編集のRequest specでのテストを行った際の備忘録です。
初心者なので細かい記述や命名は参考にしないことをお勧めします。

##環境
・Rails 5.2.4.1
・Ruby 2.6.5
・rspec-rails 3.9.0
・factory_bot_rails 5.1.1
deviseで登録/ログインシステムを構築しています。

RSpecとFactory_botの導入についてはこちらが参考になりました。
参考記事：[RailsアプリへのRspecとFactory_botの導入手順](https://qiita.com/Ushinji/items/522ed01c9c14b680222c)

#事前準備

###ログインヘルパーを使えるようにしておく
テスト環境でログインを実行するためにはヘルパーを用意する必要があります。
rails_helper.rbにコンフィグを追加することにより、spec/requests/以下でsign_inヘルパーを使えるようにしておきます。

```ruby:rails_helper.rb
...
RSpec.configure do |config|
  config.include Devise::Test::IntegrationHelpers, type: :request #sign_inヘルパーを提供してくれます
  config.include FactoryBot::Syntax::Methods #ついでにFactoryBotもincludeしておきます
end
```

これでRequestテストの際にsign_inヘルパーが使えるようになりました。

###factoriesフォルダでテストデータを作る
factoriesフォルダ内でFactory_botを使ってテストに使うユーザーのデータを作成します。
emailにはデータを生成する毎に通し番号をふってユニークな値を作るようにしています。

```ruby:spec/factories/users.rb
FactoryBot.define do
  factory :user do #factory :testuser, class: User do のようにクラスを明示すればモデル名以外のデータも作れます。
    name { "test" }
    sequence(:email) { |n| "TEST#{n}@example.com" }
    password { "testuser" }
  end
end
```

このようにテストデータを定義することでspecテスト内でテスト用データを生成することができるようになります。

```ruby
# DBへレコード生成
@user = create(:user)

# モデルのみの作成
@user = build(:user)

# userを生成しておく
let(:user) { create(:user) }

# ハッシュとして使えるパラメータuser_paramsを生成しておく
let(:user_params) { attributes_for(:user) }
```

なお、先ほど config.include FactoryBot::Syntax::Methods を追加したことでFactoryBot.と指定せずにcreateなどが使えるようになっています。

##テストの基本構造
describeでテストをグループ化、contextで条件を分け、itで条件ごとの結果を検査する、という流れがRSpecテストの基本構造です。
結果の検査にはexpectという機能を使って期待する値と実際の結果が等しいかを比較します。
例を載せておきます。

>```ruby
>RSpec.describe User do
>  describe '#greet' do
>    context '12歳以下の場合' do
>      it 'ひらがなで答えること' do
>        user = User.new(name: 'たろう', age: 12)
>        expect(user.greet).to eq 'ぼくはたろうだよ。'
>      end
>    end
>    context '13歳以上の場合' do
>      it '漢字で答えること' do
>        user = User.new(name: 'たろう', age: 13)
>        expect(user.greet).to eq '僕はたろうです。'
>      end
>    end
>  end
>end
>```

RSpecの基礎に関しては以下の記事が非常に参考になります。
引用：[使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」](https://qiita.com/jnchito/items/42193d066bd61c740612)


#Request specを書いていく
deviseで生成されたアクションの中からいくつか書いていきます。

## POST #create

```ruby:spec/requests/devise_users_spec.rb
require 'rails_helper'

RSpec.describe "UserAuthentications", type: :request do
  let(:user) { create(:user) }
  let(:user_params) { attributes_for(:user) }
  let(:invalid_user_params) { attributes_for(:user, name: "") }

  describe 'POST #create' do
    before do
      ActionMailer::Base.deliveries.clear
    end
    context 'パラメータが妥当な場合' do
      it 'リクエストが成功すること' do
        post user_registration_path, params: { user: user_params }
        expect(response.status).to eq 302
      end

      it '認証メールが送信されること' do
        post user_registration_path, params: { user: user_params }
        expect(ActionMailer::Base.deliveries.size).to eq 1
      end

      it 'createが成功すること' do
        expect do
          post user_registration_path, params: { user: user_params }
        end.to change(User, :count).by 1
      end

      it 'リダイレクトされること' do
        post user_registration_path, params: { user: user_params }
        expect(response).to redirect_to root_url
      end
    end

    context 'パラメータが不正な場合' do
      it 'リクエストが成功すること' do
        post user_registration_path, params: { user: invalid_user_params }
        expect(response.status).to eq 200
      end

      it '認証メールが送信されないこと' do
        post user_registration_path, params: { user: invalid_user_params }
        expect(ActionMailer::Base.deliveries.size).to eq 0
      end

      it 'createが失敗すること' do
        expect do
          post user_registration_path, params: { user: invalid_user_params }
        end.to_not change(User, :count)
      end

      it 'エラーが表示されること' do
        post user_registration_path, params: { user: invalid_user_params }
        expect(response.body).to include 'prohibited this user from being saved'
      end
    end
  end
end
```

postリクエストのパラメータにletで用意したものを使ったり、httpステータスからリクエストの成功を確認したり、ActionMailerの機能で認証メールが送信されたことを確認したりしています。
$ rspec コマンドを実行し、テストが通りました。

## GET #edit

```ruby:spec/requests/users_autentications_spec.rb
  ...
  describe 'GET #edit' do
    subject { get edit_user_registration_path }
    context 'ログインしている場合' do
      before do
        user.confirm
        sign_in user
      end
      it 'リクエストが成功すること' do
        is_expected.to eq 200
      end
    end
    context 'ゲストの場合' do
      it 'リダイレクトされること' do
        is_expected.to redirect_to new_user_session_path
      end
    end
  end
  ...
```

###注意点
deviseでメール認証機能をつけている場合はサインインの前に user.confirm を行い認証を済ませておく必要があります。

## GET #show
これはdeviseによる生成ではなく自作したアクションです。

###エラーの捉え方
get "/users/#{user_id}" で存在しないユーザーのプロフィールを見ようとした場合、エラーが発生したとします。

```ruby:spec/requests/users_autentications_spec.rb
  ...
  describe 'GET #show' do
    ...
    context 'ユーザーが存在しない場合' do
      it 'エラーが発生すること' do
        user_id = user.id
        user.destroy
        expect{ get "/users/#{user_id}" }.to raise_error ActiveRecord::RecordNotFound
      end
    end
  end
  ...
```

上のコードではexpectに渡すgetリクエストを{}で囲んでブロックとしているため、検査対象がブロック内の返り値となりエラーを捉えることができます。
普通にexpect()として渡そうとした場合はexpectメソッドに渡される前にgetリクエストが実行されエラーが発生するため検査することが出来ません。

参考記事：[RSpec でエラーを捉えらんないアレなミス](https://qiita.com/jiz/items/0cd302bb16ce55d231cb)

##最後に
今回は認証システムのRequest specを通じてRSpecの基礎について学習しましたが、この他にも様々な機能や構文があるので引き続き学んでいこうと思います。


余談ですが最初IntegrationHelpersの存在に気付かずsupportフォルダでsign_inヘルパーを自作するという遠回りをしてしまいました。。。
参考記事：[type: :requestのテストでsign_in/sign_outする](https://qiita.com/kazuooooo/items/1677f2a3bddf3a8df23f)
　　　　　[Rails5でコントローラのテストをController specからRequest specに移行する](https://qiita.com/t2kojima/items/ad7a8ade9e7a99fb4384#postcreate)