RSpecとFactoryBotを利用してRailsアプリの単体テストを実施してみた

# 概要
ポートフォリオとして作成した個人開発アプリのテストを実施したので
個人的なアウトプットとしてテストの流れを振り返りたいと思います。
今回はモデルのバリデーションに関するテストの基礎的な内容になります。

# 前提
プログラミングにおけるテストとは、「プログラムが意図した通りに動くことを確かめる」ことを指します。

# 環境
Ruby 2.5.1
Rails 5.2.3

gem 'devise'を使用してユーザーモデル作成済み

# はじめに
まずはテストに使用するGemをインストールしていきます。

#### 今回使用するGem
- rspec-rails　▶︎RSpecというテストに特化した言語のRails用gem
- factory_bot　▶︎簡単にダミーのインスタンスを作成できるgem

```ruby:Gemfile
group :development, :test do
　省略

  gem 'factory_bot_rails'
  gem 'rspec-rails'
end
```

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

これでgemのインストールが完了。

# RSpecの設定をする
まずはRSpec用のファイルを生成する必要があるので下記のコマンドを実行する。

```:ターミナル
$ rails g rspec:install
```

これで下記のファイルが生成される。

```:ターミナル
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb
```

#### .rspecに以下の記述を追加する
```ruby:.rspec
--format documentation
```
<br>
ここまでくれば下記のコマンドでテストは実行できます。

```:ターミナル
$ bundle exec rspec
```

# モデルクラスのテストコードを書く
いよいよテストコードを書いていくわけですが
テストコードを書くspecファイルは途中で生成されたspecという名前のディレクトリに配置します。
また、モデルのspecファイルはモデル用のディレクトリにまとめておくため、テキストエディタでディレクトリとファイルを作成していきましょう。

1. specディレクトリの配下にmodelsというディレクトリを作成する。
2. spec/modelsディレクトリの中にモデル用specファイルを作成する。

**※specファイルの命名規則はspecファイルは、対応するクラス名_spec.rbという名前になります。**

今回はUserモデルのバリデーションに関するテストを実施するという想定で進めていくのでuser_spec.rbを作成します。

まずは新規ユーザー登録時の各バリデーションが適用されるか、一つずつテストしていきます。
今回、Userモデルのバリデーションは以下の条件です。

```ruby:app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  validates :nickname, :email, :password, :password_confirmation, presence: true
end
```

validatesのpresence: trueが適用されている4カラムをテストしていきます。
<br><br><br>

まずは基本となる記述でnicknameのバリデーションをテストします。

```ruby:spec/models/user_spec.rb
require 'rails_helper'
describe User do
  describe '#create' do
    it "nicknameがない場合は登録できないこと" do
     user = User.new(nickname: "", email: "test-account@gmail.com", password: "１２３４５６７", password_confirmation: "1234567")
     user.valid?
     expect(user.errors[:nickname]).to include("can't be blank")
    end
  end
end
```

1行目はテストする上で読み込むファイルを記載。
2行目はモデルクラスを記載。
3行目はテストするアクション名を記載。

4~8行目のitからendの括りが一つあたりのテストコードになります。
4行目はテストする内容を記載。
5行目はインスタンスの作成を記載するのですが、nicknameが空という事以外はリアルな想定で記載。
6行目は作成したインスタンスに大してvalid?メソッドを記載すると、ActiveRecord::Baseを継承しているクラスのインスタンスを保存する際に「バリデーションにより保存ができない状態であるか」を確かめることができます。
7行目は6行目の内容に対して予想されるテスト結果を記載します。
valid?メソッドの返り値はtrue or falseですが、valid?メソッドを利用したインスタンスに対してerrorsメソッドを利用すると、バリデーションにより保存ができない状態である場合なぜできないのかを確認することができます。
今回の場合、to以下のincludeマッチャを利用して「"can't be blank"というエラーメッセージが出るだろう」という予想をします。

ザックリ日本語で言うと
「ユーザーのニックネームが空だから、"空にはできませんよ"というエラーが返ってくるだろうと予想を書く」
といった感じです

一旦ここでテストを実行してみましょう

```:ターミナル
$ bundle exec rspec

1 example, 0 failures
```

上の表示のように、"1つのテストに対して失敗が0"というメッセージが表示されればOKです。

あとは他のバリデーションもテストしていくのですが、
最初に導入したfactory_botを使用することでuserインスタンス作成部分のテストコードを共通化できるので、かなり便利です。

# factory_botを使用する
まずはspecディレクトリ配下にfactoriesというディレクトリを作成しましょう。
その中にusers.rbというファイルを作成します。
**※ファイルの命名はモデル名複数形で統一**

そして空のファイルにモデルのインスタンス作成で共通となる部分を記述していきます。

```ruby:spec/factories/users.rb
FactoryBot.define do

  factory :user do
    nickname              {"田中太郎"}
    email                 {"test-account@gmail.com"}
    password              {"1234567"}
    password_confirmation {"1234567"}
  end

end
```
特に説明することもありませんが
テストコードでユーザーモデルのインスタンスを作成すると記載した内容で毎回実行してくれます。

```ruby
#これが
user = User.new(nickname: "田中太郎", email: "test-account@gmail.com", password: "1234567", password_confirmation: "1234567")
#これで実現できる
user = FactoryBot.build(:user)
```

なんとさらにこのコードを簡略化できます。
user_spec.rbの中に'rails_helper'を読み込む記述がしてありますが、
このrails_helper.rbファイルの中に少し手を加えます。

```ruby:spec/rails_helper.rb
#省略
RSpec.configure do |config|
  #下記の記述を追加
  config.include FactoryBot::Syntax::Methods

  #省略

end
```

これで準備は完了です。
そうするとですね...。

```ruby
#これが
user = User.new(nickname: "田中太郎", email: "test-account@gmail.com", password: "1234567", password_confirmation: "1234567")
#これで実現できる
user = FactoryBot.build(:user)
#さらに省略できる
user = build(:user)
```

かなりコード量が減りますね...。

自分は最初これを知ったとき、全部のカラムに値を入れてるけど、バリデーションのテストする上で空のカラムとか再現できるの？
という疑問がありました➡︎問題なくできます

残りのテストコードで見ていきましょう。

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

describe User do
  describe '#create' do
    it " nicknameがない場合は登録できないこと" do
      user = build(:user, nickname: "")
      user.valid?
      expect(user.errors[:nickname]).to include("can't be blank")
    end

    it "emailがない場合は登録できないこと" do
      user = build(:user, email: "")
      user.valid?
      expect(user.errors[:email]).to include("can't be blank")
    end
    
    it "passwordがない場合は登録できないこと" do
      user = build(:user, password: "")
      user.valid?
      expect(user.errors[:password]).to include("can't be blank")
    end

    it "passwordが存在してもpassword_confirmationがない場合は登録できないこと" do
      user = build(:user, password_confirmation: "")
      user.valid?
      expect(user.errors[:password_confirmation]).to include("doesn't match Password")
    end
    
    it " passwordが5文字以下であれば登録できないこと " do
      user = build(:user, password: "00000", password_confirmation: "00000")
      user.valid?
      expect(user.errors[:password]).to include("is too short (minimum is 6 characters)")
    end

    #登録ができる場合のテストも実施
    it "nicknameとemail、passwordとpassword_confirmationが存在すれば登録できること" do
      user = build(:user)
      expect(user).to be_valid
    end

    it " passwordが6文字以上であれば登録できること " do
      user = build(:user, password: "000000", password_confirmation: "000000")
      user.valid?
      expect(user).to be_valid
    end
  end
end
```

上記の解説です

```ruby
user = build(:user)
user = build(:user, nickname: "")
```
2行目のように再度カラム名と値を指定することで
事前にセットした値を上書きできます。
この例の場合はnickname: "田中一郎" をnickname: ""　に上書きしています。
こうすることで事前にセットした値を柔軟に変えることができます（nilでも可）
<br>

```ruby
it "passwordが存在してもpassword_confirmationがない場合は登録できないこと" do
      user = build(:user, password_confirmation: "")
      user.valid?
      expect(user.errors[:password_confirmation]).to include("doesn't match Password")
    end
```

また、上記のincludeマッチャ部分ですが
"can't be blank"もそうだけど
"doesn't match Password"っていうエラーメッセージは
どこから来たの？
それっぽいことを書こうとは思うんだけど
最初からわからないとエラーメッセージの予想とかできなくね...？

そう思っていた時期が**私にもありました（注:まだ初学者です）**
<br><br><br>

これらのエラーメッセージはRailsのGemで元々用意されているモノですが
極論、これでもテストは実行できます

```ruby
.to include("")
```

そうすると、テストの実行結果がターミナルに表示されるわけですが
あなたは、""というエラーメッセージが出ると予想していたけど実際出たエラーメッセージは"doesn't match Password"だったわよ！
と返ってきます。
<br><br>

そうすると、
ああ！そうそう！ワイがテストで書きたかったのはそういうことなんや！
と、わかるわけですね

コンソールを開いて流れを検証して確認するということもできますが
初学者レベルで個人開発アプリのテスト量が少ない場合はこの方法で十分な気がします。
（怒られそう）


また、最後の2つのテストはユーザー登録ができる場合のテストをしているわけですが
be_validマッチャというのが出てきます。
これは"全てのバリデーションをクリアするだろう"という場合に使用します。


最後にテストを実行してみて

```:ターミナル
$ bundle exec rspec

7 example, 0 failures
```

テストの数に対して失敗が0ならOKです。


#まとめ
RSpecとFactoryBotを利用して、Railsの簡単なモデルテストを振り返ってみました。
今回実施したテストはほんの一部ですが、基本的な流れと小ネタを書いたので学習初めてまもない方の一助となりましたら幸いです。
<br>
また、記載している内容で不備等ございましたら教えていただけると助かります。
最後に私の学習中に参考にさせていただいたQiita記事を記載しておきます。
ありがとうございました。

#参考記事
- [使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」](https://qiita.com/jnchito/items/42193d066bd61c740612#describe--it--expect-%E3%81%AE%E5%BD%B9%E5%89%B2%E3%82%92%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B)
- [使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」](https://qiita.com/jnchito/items/2e79a1abe7cd8214caa5#%E4%BB%8A%E5%9B%9E%E8%AA%AC%E6%98%8E-%E3%81%97%E3%81%AA%E3%81%84-%E5%86%85%E5%AE%B9)