[Rails]RSpecでモデルのテスト(FactoryBot使用)

テストを行うことによって、バグを発見できたり、要件通りに開発できている保証にもなるので、テストはかなり重要なものになります。

RSpecでのモデルのテスト方法を調べた中で、各記事によって方法が違っていたので、今回はFactoryBotを使用したパターンで自分なりにまとめておきます。

ではいってみましょう！

#開発環境

> ruby 2.6.3
> Rails 5.2.6

#前提

- FactoryBot使用
- Faker使用
- capybara使用

RSpecの準備、FactoryBotでのテストデータの定義等は、下記記事にまとめてますので、まだの方は先にこちらをどうぞ。
[[Rails]RSpecでテストを行う準備(FactoryBot使用)](https://qiita.com/nakachan1994/items/29369470d50d0fcd0f6c)

#モデルのテストとは

テスト項目は様々なものがあるのですが、今回はモデルのテストについて解説していきます。

モデルのテストは、そのままモデルに関するテストですｗ

なにかしらの投稿サイトであれば、
フォームを送信してそのデータが正常なときに、保存されるか、
バリデーションエラーが出たときに、エラーが返ってくるか、
の確認をしていきます。

空白で保存されるとエラーが出ないといけないのに、そのまま保存されるのは、十分バグとなりますので、テストを実行して確認していきます。

#モデルのテストを実行するファイルを作る

まずは、モデルに関するテストコードを書いていくファイルから作っていきます。

####①specフォルダの配下にmodelsフォルダを作成

####②spec/modelsフォルダ配下に各モデルのファイルを作っていく

今回は、
user_spec.rb
post_spec.rb
like_spec.rb
comment_spec.rb
contact_spec.rb
を作成します。

自分の開発してるアプリによって、モデルは異なるので、各自自分のものに変更してください。

####③rails_helperを読み込む

作成した各モデルのファイルで、rails_helperの設定を読み込んでいきます。

rails_helperはRSpecの設定が記述してあるファイルです。

コードを１行書くだけで、読み込めるので先に記述しておきます。
最初にまとめて書いておくことで、書き忘れを防げます！

```spec/models/モデル名_spec.rb
require 'rails_helper'
```

#テストコードを書いていく

ではここから、実際にテストコードを書いていきます。
モデルのテストでは、正常に保存できるかと、バリデーションのエラーが返ってくるかを確認していくので、各モデルのバリデーションもあわせて確認していきます。

RSpecでのテストには、`マッチャーの理解が重要`です。
まだマッチャーがなにかわからない方はこちらの記事を読んだあとに戻ってきてください。
理解が十分にできるはずです。
[[Rails]RSpecでよく使うマッチャー10選](https://qiita.com/nakachan1994/items/e9938d70b3f6345368b2)

####userモデル

```models/user.rb
validates :name, presence: true, uniqueness: true, length: { maximum: 10 }
validates :email, presence: true, uniqueness: true
```

解説はコード内にコメントアウトで記載しています。

```spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  describe 'モデルのテスト' do
    it "有効なuserの場合は保存されるか" do
      # FactoryBotで作ったデータが有効であるか確認しています
      expect(build(:user)).to be_valid
    end

    context "空白のバリデーションチェック" do
      it "nameが空白の場合にエラーメッセージが返ってくるか" do
        # userにnameカラムを空で保存したものを代入
        user = build(:user, name: nil)
        # バリデーションチェックを行う
        user.valid?
        # nameカラムでエラーが出て、エラーメッセージに"を入力してください"が含まれているか？
        expect(user.errors[:name]).to include("を入力してください")
      end
      it "emailが空白の場合にエラーメッセージが返ってくるか" do
        # emailのバリデーションチェック
        user = build(:user, email: nil)
        user.valid?
        expect(user.errors[:email]).to include("を入力してください")
      end
      it "passwordが空白の場合にエラーメッセージが返ってくるか" do
        # passwordのバリデーションチェック
        user = build(:user, password: nil)
        user.valid?
        expect(user.errors[:password]).to include("を入力してください")
      end
    end

    it "nameの文字数が11文字以上の場合エラーメッセージが返ってくるか" do
      # nameカラムに"hogehogehoge"(11文字以上)の文字列を入れる
      user = build(:user, name: "hogehogehoge")
      user.valid?
      expect(user.errors[:name]).to include("は10文字以内で入力してください")
    end

    context "一意性制約の確認" do
      # itの前に@userにbuild(:user)を代入
      before do
        @user = build(:user)
      end

      it "同じnameの場合エラーメッセージが返ってくるか" do
        # @userを保存
        @user.save
        # another_userにbuild(:user)を保存
        another_user = build(:user)
        # another_userのnameカラムに@userと同じnameを代入
        another_user.name = @user.name
        # @userと同じnameになるので、バリデーションチェックに引っかかる
        another_user.valid?
        expect(another_user.errors[:name]).to include("はすでに存在します")
      end
      it "同じemailの場合エラーメッセージが返ってくるか" do
        # emailバージョン
        @user.save
        another_user = build(:user)
        another_user.email = @user.email
        another_user.valid?
        expect(another_user.errors[:email]).to include("はすでに存在します")
      end
    end
  end
end
```

####postモデル

```models/post.rb
validates :image, presence: true
validates :content, presence: true, length: { maximum: 140 }
```

解説はコード内にコメントアウトで記載しています。

```spec/models/post_spec.rb
require 'rails_helper'

RSpec.describe Post, type: :model do
  describe 'モデルのテスト' do
    it "有効なpostの場合は保存されるか" do
      expect(build(:post)).to be_valid
    end
    
    context "空白のバリデーションチェック" do
      it "imegeが空白の場合にエラーメッセージが返ってくるか" do
        post = build(:post, image: nil)
        post.valid?
        expect(post.errors[:image]).to include("を入力してください")
      end
      it "contentが空白の場合にエラーメッセージが返ってくるか" do
        post = build(:post, content: nil)
        post.valid?
        expect(post.errors[:content]).to include("を入力してください")
      end
    end

    it "contentの文字数が141文字以上の場合エラーメッセージが返ってくるか" do
      post = create(:post)
      # Faker::Lorem.characters(number: 141)でランダムな文字列を141字で作成できる
      post.content = Faker::Lorem.characters(number: 141)
      post.valid?
      expect(post.errors[:content]).to include("は140文字以内で入力してください")
    end
  end
end
```

####commentモデル

```models/comment.rb
validates :comment, presence: true, length: { maximum: 50 }
```

解説はコード内にコメントアウトで記載しています。

```spec/models/comment_spec.rb
require 'rails_helper'

RSpec.describe Comment, type: :model do
  describe 'モデルのテスト' do
    it "有効なcommentの場合は保存されるか" do
      expect(build(:comment)).to be_valid
    end

    it "commentが空白の場合にエラーメッセージが返ってくるか" do
      # commentモデルのcommentカラムです。ややこしくてすいません
      comment = build(:comment, comment: nil)
      comment.valid?
      expect(comment.errors[:comment]).to include("を入力してください")
    end

    it "commentの文字数が50文字以上の場合エラーメッセージが返ってくるか" do
      comment = build(:comment)
      comment.comment = Faker::Lorem.characters(number: 51)
      comment.valid?
      expect(comment.errors[:comment]).to include("は50文字以内で入力してください")
    end
  end
end
```

####contactモデル

```models/contact.rb
validates :name, presence: true, length: { maximum: 20 }
validates :email, presence: true
validates :content, presence: true, length: { minimum: 10 }
```

今までのコードの解説と同じになるので、省略します。

```spec/models/contact_spec.rb
require 'rails_helper'

RSpec.describe Contact, type: :model do
  describe 'モデルのテスト' do
    it "有効なcontactの場合は保存されるか" do
      expect(build(:contact)).to be_valid
    end

    context "空白のバリデーションチェック" do
      it "nameが空白の場合にエラーメッセージが返ってくるか" do
        contact = build(:contact, name: nil)
        contact.valid?
        expect(contact.errors[:name]).to include("を入力してください")
      end
      it "emailが空白の場合にエラーメッセージが返ってくるか" do
        contact = build(:contact, email: nil)
        contact.valid?
        expect(contact.errors[:email]).to include("を入力してください")
      end
      it "contentが空白の場合にエラーメッセージが返ってくるか" do
        contact = build(:contact, content: nil)
        contact.valid?
        expect(contact.errors[:content]).to include("を入力してください")
      end
    end

    context "文字数のバリデーションチェック" do
      it "nameの文字数が21文字以上の場合エラーメッセージが返ってくるか" do
        contact = build(:contact, name: "hogehogehogehogehogehoge")
        contact.valid?
        expect(contact.errors[:name]).to include("は20文字以内で入力してください")
      end
      it "contentの文字数が10文字以下の場合エラーメッセージが返ってくるか" do
        contact = build(:contact, content: "hoge")
        contact.valid?
        expect(contact.errors[:content]).to include("は10文字以上で入力してください")
      end
    end
  end
end
```

####likeモデル

likeモデルはバリデーションなしなので、正常に保存できるかだけを確認していきます。

```spec/models/like_spec.rb
require 'rails_helper'

RSpec.describe Like, type: :model do
  describe 'モデルのテスト' do
    it "有効なuserの場合は保存されるか" do
      expect(build(:like)).to be_valid
    end
  end
end
```

#テストを実行

これでモデルのテストコードがかけたので、テストを実行していきます。

ターミナルコマンドでテストを実行します。

```
$ bundle exec rspec spec/models
```

エラーが出ず、緑の文字で表示されていればテスト成功です！

#さいごに

テストコードを書いていくところでは、実際のコードを全てのせてみました。
これを参考にしてテスト実行してみてください。