【Rails / RSpec】 Modelテストを書く（Shoulda Matchers・FactoryBot 使用）

# はじめに
テストを全く記述したことがない状態から RSpec を実際に記述しながら学びました。理解した内容を実際に記述したコードとともに記事に残したいと思います。

※RSpec 初心者です。誤り、ご指摘などございましたらコメントいただければ非常にありがたいです。

# 前提
- Usersテーブル
  - カラム： ``email: string``と``password: string``のみ
  - Booksテーブルと1対多の関連付けがされている
- テストデータの生成には Gem ``FactoryBot``を使用
- テストには簡単に記述できる Gem ``Shoulda Matchers``を使用

``Shoulda Matchers``を使うと何行も記述しないとできなかったテストが１行で書けてしまいます。何が行われているかわからない部分もありますが、今回記述した内容の他にも便利な構文が用意されておりますので、興味のある方は参考サイトからぜひ確認してみることをお勧めします。

# 参考サイト
- [使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」](https://qiita.com/jnchito/items/42193d066bd61c740612#let-%E3%81%AE%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88%E3%82%92%E6%B4%BB%E3%81%8B%E3%81%97%E3%81%A6-age-%E3%82%82-let-%E3%81%A7%E7%BD%AE%E3%81%8D%E6%8F%9B%E3%81%88%E3%82%8B)
- [使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」](https://qiita.com/jnchito/items/2e79a1abe7cd8214caa5#be)
- [Shoulda Matchers](http://matchers.shoulda.io/docs/v4.3.0/)
- [FactoryBot(FactoryGirl)チートシート](https://qiita.com/morrr/items/f1d3ac46b029ccddd017)
- [【RSpec】で書く「Rails Tutorial　６章」のテスト](https://qiita.com/Goi350ml/items/7dac5bd2d3d522466d94#comments)

非常にわかりやすく大変参考にさせていただきました！
ありがとうございました！

# Model

```app/models/user.rb
class User < ApplicationRecord
  has_many :books # booksと１対多の関連付け
  has_secure_password
  before_save { email.downcase! }

  validates :email, presence: true,
                    length: { maximum: 255 },
                    format: { with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i },
                    uniqueness: { case_sensitive: false }
  validates :password, presence: true,
                       length: { minimum: 6 }
end
```

# テスト
合計9つのテストを行っている

## ファイル全体

```spec/models/user_spec.rb
# rails_helperファイルを読み込み
require 'rails_helper'

# Userモデルのテストなので User, type: :model としている
RSpec.describe User, type: :model do

  describe 'has_many' do
    # has_many :books のテスト
    it { should have_many(:books) }
  end

  describe 'has_secure_password' do
    # has_secure_password のテスト
    it { should have_secure_password }
  end
  
  describe 'validation' do
    context 'email' do
      # FactoryBot でテストデータを作成
      let(:user) { create(:user) }
      let(:upcase_email_user) { build(:user, :upcase_email) }

      # presence: true のテスト
      it { should validate_presence_of(:email) }
      # length: { maximum: 255 } のテスト
      it { should validate_length_of(:email).is_at_most(255) }

      # uniqueness: { case_sensitive: false } のテスト
      it 'emailの重複保存を許容しないこと' do
        duplicate_user = user.dup
        duplicate_user.email = user.email.upcase
        expect(duplicate_user).to be_invalid
      end
     
      # format: { with: XXXXX } のテスト
      it '指定formatに合わないemailを許容しないこと' do
        invalid_emails = %w[user@foo,com user_at_foo.org example.user@foo.foo@bar_baz.com foo@bar+baz.com foo@bar..com]
        invalid_emails.each do |invalid_email|
          user.email = invalid_email
          expect(user).to be_invalid
        end
      end

      # before_save { email.downcase! } のテスト
      it 'downcase!が正常に動作していること' do
        upcase_email_user.save!
        expect(upcase_email_user.email).to eq 'test@test.com'
      end
    end

    context 'password' do
      # presence: true のテスト
      it { should validate_presence_of(:password) }
      # length: { minimum: 6 } のテスト
      it { should validate_length_of(:password).is_at_least(6) }
    end
  end
end

```

## 各記述の解説

### テストの宣言
``RSpec.describe モデル名, type: :テスト形式 do``となっており、この記述でブロック内でのUserモデルのテストが実行できる

```.rb
RSpec.describe User, type: :model do
```

### has_many と has_secure_password のテスト

``Shoulda Matchers``を利用してモデルの関連付けと、 ``has_secure_password`` の適用を検証

```.rb
# has_many :books のテスト
describe 'has_many' do
  it { should have_many(:books) }
end

# has_secure_password のテスト
describe 'has_secure_password' do
  it { should have_secure_password }
end
```

### describeとcontext でテスト記述を分ける

```.rb
describe 'validation' do
    context 'email' do
      # emailのバリデーションテストを記述
    end
    context 'password' do
      # passwordのバリデーションテストを記述
    end
end
```

### FactoryBotでテストデータを作成
ファクトリーボットで作成するテストデータを専用ファイルに定義

```spec/factories/users.rb
FactoryBot.define do
  factory :user do
    # データを生成する毎に通し番号をふってユニークな値を作るようにしている
    sequence(:email) { |n| "test#{n}@test.com" }
    password { 'password' }

    trait :upcase_email do
      email { 'TeSt@TEst.Com' }
    end
  end
end
```

上記ファイルを元に``let``でテストデータを作成しており、このテストデータは記述したブロック内の``it .. do ~~ end``ブロックで使用できる。
コメントアウトで``binding.pry``で``user``と``upcase_email_user``の中身を確認した結果を記載している。

```.rb
  let(:user) { create(:user) }
  # user = <User:0x0000560469431138 id: 2610, email: "test1@test.com", password_digest: "[FILTERED]", created_at: Tue, 27 Oct 2020 14:03:04 JST +09:00, updated_at: Tue, 27 Oct 2020 14:03:04 JST +09:00>
  let(:upcase_email_user) { build(:user, :upcase_email) }
  # upcase_email_user = <User:0x00005604690c12c8 id: nil, email: "TeSt@TEst.Com", password_digest: "[FILTERED]", created_at: nil, updated_at: nil>
```

※上記``create``と``build``は``FactoryBot``の記述を省略できる方法を適用。

### email バリデーション
#### presence:true length: { maximum: 255 } のテスト(Shoulda Matchers)
``Shoulda Matchers``を用いてテストを記述

```.rb
  # presence: true をテスト
  it { should validate_presence_of(:email) }
  # length: { maximum: 255 } をテスト
  it { should validate_length_of(:email).is_at_most(255) }
```

#### uniqueness: { case_sensitive: false }のテスト
``dupメソッド``で``user``のコピーデータを作成。
``let(:user) { create(:user) }``ですでに登録済みの``user``と同じ``email``を持つ``duplicate_user``が保存されないことを検証。
テストの前にemailを``upcase``で大文字にすることで、大文字でも同一なemailと判定するかも合わせて検証している。

``be_invalid``はバリデーションで引っかかれば（エラーが出れば）パスする

```.rb
it 'emailの重複保存を許容しないこと' do
  duplicate_user = user.dup
  # この時点での中身
  # duplicate_user = <User:0x000055f1e1d71e90 id: nil, email: "test1@test.com", password_digest: "[FILTERED]", created_at: nil, updated_at: nil>
  duplicate_user.email = user.email.upcase
  # user.email.upcase後の中身
  # #<User:0x000055f1e1d71e90 id: nil, email: "TEST1@TEST.COM", password_digest: "[FILTERED]", created_at: nil, updated_at: nil>

  expect(duplicate_user).to be_invalid  # duplicate_user.invalid? が true になればパスする
end
```

#### format: { with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i } のテスト


```.rb
it '指定formatに合わないemailを許容しないこと' do
  # 指定formatに合致しない文字列の配列を６つ用意
  invalid_emails = %w[user@foo,com user_at_foo.org example.user@foo.foo@bar_baz.com foo@bar+baz.com foo@bar..com]
  # userのemailにformatに合致しないemailをいれて検証
  invalid_emails.each do |invalid_email|
    user.email = invalid_email
    expect(user).to be_invalid # duplicate_user.invalid? が true になればパスする
  end
end
```

#### before_save { email.downcase! } のテスト

``upcase_email_user``のemail ``TeSt@TEst.Com`` がsave後には``test@test.com``になっていることを検証しており、
``expect(X).to eq Y``は「XがYに等しくなること」をテストしている。

```.rb
it 'downcase!が正常に動作していること' do
  # before_saveはsaveの直前に呼び出されるためsave!する
  upcase_email_user.save!
  expect(upcase_email_user.email).to eq 'test@test.com'
end
```
### password バリデーション

#### presence: true、 length: { maximum: 6 } のテスト(Shoulda Matchers)
emailと同様に``Shoulda Matchers``を用いてテストを記述

```.rb
context 'password' do
  # presence: true のテスト
  it { should validate_presence_of(:password) }
  # length: { maximum: 6 } のテスト
  it { should validate_length_of(:password).is_at_least(6) }
end
```

# 最後に
RSpec の学びはじめは構文に慣れておらず非常に苦労しました。しかし、わかりやすい解説の記事も多く、慣れてきて思った通りにテストがパスすると楽しくなってきます。
今回はモデルテストの記事を書きましたが、リクエストテストも学んだのでそちらも今後記事にできたらいいと思っています。