【Rails】はじめてのSystemSpec(RSpec)

# はじめに

<font color="Red">RSpec</font>(テスティングフレームワーク)を使用してSystem Testを行います。
尚、RSpecではSystem TestのことをSystem Specと呼びます。

System Specを書いたことがない人でも書けるように初歩的な
**WebアプリケーションにおけるユーザーCRUD関連**
をテストします。

この記事はテストのみの記事ですので前提条件があります。

# 前提条件

ユーザーの新規作成、編集、ができて、ログインログアウトができて、ログイン時、非ログイン時で機能の使い分けができればOKです。

筆者はユーザー認証にSorceryを使用していますが、Deviseでもgemを使用していなくてもユーザー認証ができればテストは可能です。

>
①ユーザー(User)の新規作成と編集機能が実装済
②ユーザー認証(ログイン、ログアウト)機能が実装済
③非ログイン時は、ユーザーの新規作成とログインのみが機能として使える

# 環境
## 各バージョン

`Ruby 2.6.5`
`Rails 5.2.3`
`MySQL 5.7.28`
`RSpec 3.8`
`capybara 3.31.0`
`factory_bot_rails 5.1.1`

## DB設計

以後は自身の設計に合わせて読みかえてください。

![Entity Relationship Diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562358/27c9516c-e435-232e-2117-066d47c77e1f.png)


## ルーティング

```ruby:routes.rb
Rails.application.routes.draw do
  get 'login', to: 'user_sessions#new'
  post 'login', to: 'user_sessions#create'
  delete 'logout', to: 'user_sessions#destroy'
  get 'sign_up', to: 'users#new'
  resources :users
end
```

## バリデーション

最低限検証したいバリデーションのみ記述します。
①emailが一意であること。(既に登録済のemailがあったらユーザー新規作成できない)
②emailが空でないこと。(emailが空白だとユーザー新規作成できない)

```ruby:user.rb
class User < ApplicationRecord
  authenticates_with_sorcery!
  validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] }
  validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }
  validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] }

  # ここより上記はSorceryの機能に必要な記述
  validates :email, uniqueness: true, presence: true
end
```

# 実装

## 準備

1. RSpecの準備
2. Capybaraの準備
3. FactoryBotの準備
4. Rspecの実行結果を綺麗に表示
5. System Specファイルの作成
6. 用語の説明

### 1.RSpecの準備

```ruby:Gemfile
group :test do
  gem 'rspec-rails'
```

```console:ターミナル
$ bundle install
$ rails g rspec:install
$ rm -r ./test
```

これで必要・不必要なファイルの処理が完了。

### 2.Capybaraの準備

```ruby:spec/spec_helper.rb
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration

require 'capybara/rspec'
```

```ruby:Gemfile
group :test do
  gem 'rspec-rails'
  gem 'webdrivers'
end
```

```console:ターミナル
$ bundle install
$ mkdir spec/support
$ touch spec/support/driver_setting.rb
```

```ruby:spec/support/driver_setting.rb
RSpec.configure do |config|
  config.before(:each, type: :system) do
    # Spec実行時、ブラウザが自動で立ち上がり挙動を確認できる
    # driven_by(:selenium_chrome)

    # Spec実行時、ブラウザOFF
    driven_by(:selenium_chrome_headless)
  end
end
```

Specのデバッグをする際は`driven_by(:selenium_chrome)`を有効にしておくとブラウザで物理的に見ることができるのでわかりやすい。
有効のままだとSpec実行時に毎回ブラウザが立ち上がってしまうので普段はコメントアウトしておきます。

```ruby:spec/rails_helper.rb
# require only the support files necessary.
#
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }
```

`spec/support`以下が読み込まれます。

### 3.FactoryBotの準備

```ruby:Gemfile
group :development, :test do
  gem 'factory_bot_rails'
```

```console:ターミナル
$ bundle install
```

### 4.Rspecの実行結果を綺麗に表示

```ruby:.rspec
--require spec_helper
# 追記
--format documentation
```

### 5.System Specファイルの作成

ユーザー関連のSpec、投稿関連のSpec、ユーザー認証のSpec
それぞれのファイルを作成します。

```console:ターミナル
$ mkdir spec/system
$ touch spec/system/users_spec.rb
```

### 6.用語の説明

> describe、context　→　テストケースを整理・分類する
> before、it　→　テストコードを実行する

・describe 「テストの対象を記述」
・context 「テストの内容を状況・状態によって分類して記述」
・before 「その領域全体の前提条件を記述」
・it 「期待する動作を文章とコードで記述」

## FactoryBotでテストデータを作成

FctoryBotとは、`データの作成を簡単にしてくれる機能`です。
ここでuserとtaskのデータをあらかじめ作成しておきます。

```console:ターミナル
$ mkdir spec/factories
$ touch spec/factories/users.rb
```

```ruby:spec/factories/users.rb
FactoryBot.define do
  factory :user do
    sequence(:email) { |n| "person#{n}@example.com" }
    password { 'password' }
    password_confirmation { 'password' }
  end
end
```

`sequence`を使用するとデータを複数作成する際に、`n`に連番の数字を入れることによって一意の文字列を作成することができます。

## loginメソッドをModuleへ切り出し

```ruby
visit login_path
fill_in 'Email', with: user.email
fill_in 'Password', with: 'password'
click_button 'Login'
```

上記のコードがログインしている状態を再現するコードになりますが、各ファイルでいちいち書くのは面倒なのでメソッドにしてModuleへ切り出してrails_helper.rbでincludeします。

```console:ターミナル
$ touch spec/support/login_module.rb
```

```ruby:login_module.rb
module LoginModule
  def login(user)
    visit login_path
    fill_in 'Email', with: user.email
    fill_in 'Password', with: 'password'
    click_button 'Login'
  end
end
```

```ruby:rails_helper.rb
RSpec.configure do |config|
...

  # FactoryBotの省略
  config.include FactoryBot::Syntax::Methods
  # module読み込み
  config.include LoginModule
end
```

これで`login(user)`でログイン状態を再現できるようになりました。
ついでにspecを記述していく際に`FactoryBot`を省略して書けるように設定しておきます。

## users_spec.rbの実装

このファイルでは**WebアプリケーションのユーザーCRUD関連**のSpecを記述していきます。
今回テストする項目は5つ。
下記の画像のようにツリー構造(以後ツリー画像と呼びます)で分類しました。

色々な状況で分岐させて、`it(ピンク)`の部分が最終的にテストしたい部分になります。

![User_CRUD1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562358/51651970-5af2-bd39-ac2c-2465df052dd4.png)

### 雛形

まずは雛形。

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

RSpec.describe User, type: :system do

end
```

### let

次に`let`を使用してテストデータを変数のように扱えるようにしていきます。
`let`の詳しい説明は割愛しますが`let(定義名)`の定義名を使用することでテストデータを扱えるように記述します。

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

RSpec.describe User, type: :system do
  let(:user) { create(:user) }
  let(:other_user) { create(:user) }
end
```

### 黄色(describe 大)

次はツリー画像の色ごと順番に記述していきます。
下の色につれてどんどん入れ子構造にしていきます。

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

RSpec.describe User, type: :system do
  let(:user) { create(:user) }
  let(:other_user) { create(:user) }

  describe 'User CRUD' do

  end
end
```

### 紫(describe 中)

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

RSpec.describe User, type: :system do
  let(:user) { create(:user) }
  let(:other_user) { create(:user) }

  describe 'User CRUD' do
    describe 'ログイン前' do
      
    end
    describe 'ログイン後' do
      
    end
end
```

### 緑(describe 小)

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

RSpec.describe User, type: :system do
  let(:user) { create(:user) }
  let(:other_user) { create(:user) }

  describe 'User CRUD' do
    describe 'ログイン前' do
      describe 'ユーザー新規登録' do

      end
    end
    describe 'ログイン後' do
      describe 'ユーザー編集'
    end
  end
end
```
### 青(context)

```ruby:users_spec.rb
RSpec.describe User, type: :system do
...

  describe 'User CRUD' do
    describe 'ログイン前' do
      describe 'ユーザー新規登録' do
        context 'フォームの入力値が正常' do

        end
        context 'メールアドレス未記入' do

        end
        context '登録済メールアドレス' do

        end
      end
    end
    describe 'ログイン後' do
      describe 'ユーザー編集'
        context 'フォームの入力値が正常' do

        end
        context 'メールアドレス未記入' do
          
        end
    end
  end
end
```

### ピンク(it)

`it`に関しては`一つのブロックずつ`記述していきます。

#### 新規登録成功

>条件
>>黄:User CRUD
紫:ログイン前
緑:ユーザー新規登録
青:フォーム正常

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

RSpec.describe User, type: :system do
...

context 'フォームの入力値が正常' do
  it 'ユーザーの新規作成が成功' do
    # ユーザー新規登録画面へ遷移
    visit sign_up_path
    # Emailテキストフィールドにtest@example.comと入力
    fill_in 'Email', with: 'test@example.com'
    # Passwordテキストフィールドにpasswordと入力
    fill_in 'Password', with: 'password'
    # Password confirmationテキストフィールドにpasswordと入力
    fill_in 'Password confirmation', with: 'password'
    # SignUpと記述のあるsubmitをクリックする
    click_button 'SignUp'
    # login_pathへ遷移することを期待する
    expect(current_path).to eq login_path
    # 遷移されたページに'User was successfully created.'の文字列があることを期待する
    expect(page).to have_content 'User was successfully created.'
  end
end

...
end
```

#### 新規登録失敗(アドレス未記入)

>条件
>>黄:User CRUD
紫:ログイン前
緑:ユーザー新規登録
青:アドレス未記入

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

RSpec.describe User, type: :system do
...

context 'メールアドレス未記入' do
  it 'ユーザーの新規作成が失敗' do
    # ユーザー新規登録画面へ遷移
    visit sign_up_path
    # Emailテキストフィールドをnil状態にする
    fill_in 'Email', with: nil
    # Passwordテキストフィールドにpasswordと入力
    fill_in 'Password', with: 'password'
    # Password confirmationテキストフィールドにpasswordと入力
    fill_in 'Password confirmation', with: 'password'
    # SignUpと記述のあるsubmitをクリックする
    click_button 'SignUp'
    # users_pathへ遷移することを期待する
    expect(current_path).to eq users_path
    # 遷移されたページに'Email can't be blank'の文字列があることを期待する
    expect(page).to have_content "Email can't be blank"
  end
end

...
end
```

#### 新規登録失敗(登録済アドレス)

>条件
>>黄:User CRUD
紫:ログイン前
緑:ユーザー新規登録
青:登録済アドレス

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

RSpec.describe User, type: :system do
...

context '登録済メールアドレス' do
  it 'ユーザーの新規作成が失敗する' do
    # ユーザー新規登録画面へ遷移
    visit sign_up_path
    # Emailテキストフィールドにlet(:user)に定義したユーザーデータのemailを入力
    fill_in 'Email', with: user.email
    # Passwordテキストフィールドにpasswordと入力
    fill_in 'Password', with: 'password'
    # Password confirmationテキストフィールドにpasswordと入力
    fill_in 'Password confirmation', with: 'password'
    # SignUpと記述のあるsubmitをクリックする
    click_button 'SignUp'
    # users_pathへ遷移することを期待する
    expect(current_path).to eq users_path
    # 遷移されたページに'Email can't be blank'の文字列があることを期待する
    expect(page).to have_content "Email has already been taken"
  end
end

...
end
```

#### 編集成功

>条件
>>黄:User CRUD
紫:ログイン済
緑:ユーザー編集
青:フォーム正常

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

RSpec.describe User, type: :system do
...

    describe 'ログイン後' do
      before { login(user) }
        describe 'ユーザー編集' do
          context 'フォームの入力値が正常' do
            it 'ユーザーの編集が成功' do
              visit edit_user_path(user)
              fill_in 'Email', with: 'test@example.com'
              fill_in 'Password', with: 'test'
              fill_in 'Password confirmation', with: 'test'
              click_button 'Update'
              expect(current_path).to eq user_path(user)
              expect(page).to have_content 'User was successfully updated.'
            end
          end
        end
      end
    end
...
end
```

#### 編集失敗

>条件
>>黄:User CRUD
紫:ログイン済
緑:ユーザー編集
青:アドレス未記入

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

RSpec.describe User, type: :system do
...

context 'メールアドレスが未入力' do
  it 'ユーザーの編集が失敗' do
    visit edit_user_path(user)
    fill_in 'Email', with: nil
    fill_in 'Password', with: 'password'
    fill_in 'Password confirmation', with: 'password'
    click_button 'Update'
    expect(current_path).to eq user_path(user)
    expect(page).to have_content "Email can't be blank"
  end
end

...
end
```

### テスト実行

```console:ターミナル
$ bundle exec rspec spec/system/users_spec.rb
```

これで**WebアプリケーションのユーザーCRUD関連**のSystem Specの完成です!!!