Rspecでサインインメソッドを共通化して切り出す(devise使わないとき)[system spec][request spec]

# はじめに
サインイン機能の勉強のために、サインイン機能をdeviseを使わないで自作したところ、テストで大変苦労したので、学んだことを以下に記します。

今回は、system specとrequest specでテストを記載し、多くのところで使いまわせるよう、これらの処理を別ファイルに書き出しました。

実行環境は以下の通りです。

- Rails 5.2.4.2
- Rspec 4.0.1
- capybara  3.33.0

## サインイン画面について
emailとパスワードでサインインできるフォームを作っています。画像は参考画像です。
<img width="488" alt="sample.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393888/84f696f5-78b0-ba18-37a7-e3c25287f315.png">

## 基本のサインインメソッド
サインインメソッドをsystem specとrequest specとでそれぞれ表すと下記のようになります。今回は、あるショップに所属しているスタッフを作成し、そのスタッフのメールアドレスとパスワードでサインインをします。

### system spec
```ruby:spec\system\shops_spec.rb
let!(:shop) { FactoryBot.create(:shop) }
let!(:user) { FactoryBot.create(:staff, shop_id: shop.id ) }

before do
  visit login_path
  fill_in "E-mail", with: user.email
  fill_in "Password", with: user.password
  click_button 'LOGIN'
end
```

### request spec
```ruby:spec\system\shops_spec.rb
let!(:shop) { FactoryBot.create(:shop) }
let!(:user) { FactoryBot.create(:staff, shop_id: shop.id ) }

before do
  session_params = { session: { email: user.email, password: user.password } }
  post "/login", params: session_params
end
```

## 共通事項をまとめる(1) shared_context
まずは、letで従業員を定義している2行が、両者で重複していたので、それを`shared_context`を用いて、別ファイルに書き出します。

```ruby:spec\support\contexts\user_setup.rb
RSpec.shared_context "user_setup" do
  let!(:shop) { FactoryBot.create(:shop) }
  let!(:user) { FactoryBot.create(:staff, shop_id: shop.id ) }
end
```

この時、`spec\rails_helper.rb`内にある、以下の行のコメントアウトを外し、`spec\support`内のファイルやディレクトリをすべて読み込めるようにしておきます。

```ruby:spec\rails_helper.rb
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
```

こちらの設定で、spec内では以下のように記述することで、ユーザーのセットアップができるようになります。

```ruby
# 任意のspecファイル内
include_context "user_setup"
```

## 共通事項をまとめる(2) module
system specとrequest specの共通処理もそれぞれモジュールにまとめることで、いろいろなところで使いまわすことができます。今回作成したファイルは以下の通りです。

### system spec
```ruby:spec\support\sign_in_module.rb
module SignInModule
  def sign_in_as(user)
    visit login_path
    fill_in "E-mail", with: user.email
    fill_in "Password", with: user.password
    click_button 'LOGIN'
  end
end
```

なお、この時、ファイル名とモジュール名は一致させる必要があるようです。例えば、`SignInModule`なら、`sign_in_module.rb`というファイル名にする必要があります。

▼参考サイト
https://relishapp.com/rspec/rspec-core/v/3-0/docs/helper-methods/define-helper-methods-in-a-module

### request spec
```ruby:spec\support\login_request_module.rb
module SignInRequestModule
  def sign_in_request_as(user)
    session_params = { session: { email: user.email, password: user.password } }
    post "/login", params: session_params
  end
end
```

### rails_helper.rb への記載
このとき、`spec\rails_helper.rb`には、以下のように記入します。（明示的に読み込むことを記載しないと、読み込めませんでした…。）

```ruby:spec\rails_helper.rb
RSpec.configure do |config|
  # 中略
  config.include SignInModule
  config.include SignInRequestModule
  
end
```

このあたりもう少しDRYにかけそうなので、方法がわかったら追記したいです。

### specファイルでの使い方
上記のように記載することで、specファイルではこちらの方法で、それぞれサインインメソッドを呼び出すことができます。

```ruby
# 任意のsystem specファイル
include_context "user_setup"
before do
  sign_in_as(user)
end
```

```ruby
# 任意のrequest specファイル
include_context "user_setup"
before do
  sign_in_request_as(user)
end
```

## まとめ＆参考サイト
devise使わないでログイン機能を実装してやる！と頑張って作り始めたのですが、deviseはいろいろなところで手間を簡略化してくれていることがわかりました。これからはdeviseとうまく付き合っていきたいと思います。
一方で、サインインの仕組みにも詳しくなったので、エンジニアとしてのスキルアップはできたと思います。

今回参考にした情報はこちら。
[Define helper methods in a module](https://relishapp.com/rspec/rspec-core/v/3-0/docs/helper-methods/define-helper-methods-in-a-module)
[Request Specを使おう](https://qiita.com/kagesumi3m/items/10244978273ffffa9b92)
[What is the proper way to sign in as a user in an rspec request spec, without devise?](https://stackoverflow.com/questions/50031279/what-is-the-proper-way-to-sign-in-as-a-user-in-an-rspec-request-spec-without-de)
[rails5 can't access session in ActionDispatch::IntegrationTest](https://github.com/rails/rails/issues/23386)