userモデル単体テストとdeviseによるバリデーション

# Userのモデル単体テストの実装

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

  has_many :tweets
  has_many :comments
  validates :nickname, presence: true, length: { maximum: 6 }
end
~~~

バリデーションは「属性の値がDBに保存されてもよいかチェックを行う条件」を設定するものです。


presenceは、指定した属性の値の有無をチェックします。
Userモデルでは、nicknameに対して、presence: trueの条件を指定しています。

この場合、DBに保存する前にnicknameに値が存在していなければ、条件に引っかかりDBに保存されません。

lengthは、指定した属性の値の文字数制限をチェックします。
Userモデルでは、nicknameに対して、length: { maximum: 6 }の条件を指定しています。

この場合、DBに保存する前にnicknameの値が7文字以上であれば、条件に引っかかりDBに保存されません。


このようなバリデーションから、以下のような挙動を検証すべきことがわかります。

nicknameが空では新規登録できない
nicknameが7文字以上では登録できない
nicknameが6文字以下なら登録できる


さらに、Userモデルには9行目の記述で設定したものとは別に、deviseによるバリデーションも自動的に設けられています。
5行目の:validatableという記述がその役割を担っています。

自動的に設けられるバリデーションは以下のとおりです。

| 　         | email | password |
|:-----------|------------:|:------------:|
| バリデーション 　　|・存在すること<br>・一意であること<br>・@を含むこと  |・存在すること<br>・6文字以上128文字以下であること         |

: validatableで設定される条件より、以下のような挙動を検証すべきことがわかります。

・nicknameとemail、passwordとpassword_confirmationが存在すれば登録できる
・nicknameが空では登録できない
・emailが空では登録できない
・passwordが空では登録できない
・passwordとpassword_confirmationが不一致では登録できない
・nicknameが7文字以上では登録できない
・重複したemailが存在する場合は登録できない
・emailは@を含まないと登録できない
・passwordが5文字以下では登録できない
・passwordが129文字以上では登録できない

<br><br>
exampleを列挙できたため、これらをテストコードに落とし込みましょう。
~~~spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  before do
    @user = FactoryBot.build(:user)
  end

  describe 'ユーザー新規登録' do
    it 'nicknameとemail、passwordとpassword_confirmationが存在すれば登録できる' do
    end
    it 'nicknameが空では登録できない' do
    end
    it 'emailが空では登録できない' do
    end
    it 'passwordが空では登録できない' do
    end
    it 'passwordとpassword_confirmationが不一致では登録できない' do
    end
    it 'nicknameが7文字以上では登録できない' do
    end
    it '重複したemailが存在する場合は登録できない' do
    end
    it 'emailは@を含まないと登録できない' do
    end
    it 'passwordが5文字以下では登録できない' do
    end
    it 'passwordが129文字以上では登録できない' do
    end
  end
end
~~~

# 新規登録できる場合のテストコードを記述しよう

前章までは、新規登録できない場合の異常系テストを主に学習してきました。
これから学習する新規登録できる場合の正常系テストも、実装手順に大きな違いはありません。

以下のような流れで進んでいきます。

1. 検証したいモデルのインスタンスを生成する
今回はUserのモデル単体テストであるため、Userモデルのインスタンスを生成しましょう。

2章では、exampleごとにUser.newと記述して、インスタンスを生成していました。
しかし3章以降は、exampleごとにFactoryBotがインスタンスを生成しています。

そのため、itのブロックの中に、インスタンスを生成する記述を加える必要はありません。

具体的には、user_spec.rbの4,5,6行目の記述でインスタンスを生成しています。

2. 生成したインスタンスがどのような状況であればいいかを記述する
エクスペクテーションを記述します。
正常系テストのエクスペクテーションには、be_validマッチャを用います。

# be_valid

be_validとは、valid?メソッドの返り値が、trueであることを期待するマッチャです。
expectの引数に指定されたインスタンスが、バリデーションでエラーにならないものであれば、valid?の返り値はtrueとなりテストは成功します。

それでは、be_validを用いて、正常系のexampleごとにテストコードを記述してみましょう。

# 値がすべて存在する場合の挙動を確認しましょう

be_validマッチャは、valid?メソッドの返り値がtrueであることを期待するマッチャでした。
FactoryBotによって生成されるインスタンスが、バリデーションでエラーにならないか確かめてみましょう。

~~~
% rails c
[1] pry(main)> @user = FactoryBot.build(:user)
  (4.3ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
=> #<User id: nil, email: "judie@gmail.com", created_at: nil, updated_at: nil, nickname: "GP">
[2] pry(main)> @user.valid?
  User Exists? (10.3ms)  SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'judie@gmail.com' LIMIT 1
=> true
~~~

上記のように、エラーにならないことが確認できたらexitを入力して抜けましょう。
したがって、@userをexpectの引数に指定し、be_validマッチャを用いてエクスペクテーションを記述しましょう。

# エクスペクテーションを記述しましょう

コンソールで確かめた内容を、ファイルに記述します。
以下のように記述してください。

~~~spec/models/user_spec.rb
it 'nicknameとemail、passwordとpassword_confirmationが存在すれば登録できる' do
  expect(@user).to be_valid
end
~~~

# テストコードを実行しましょう

以下のコマンドを実行してください。


~~~
% bundle exec rspec spec/models/user_spec.rb 
~~~

実行結果が緑色で表示されていればテスト成功です<br><br>

# 新規登録できない場合のテストコードを記述しよう

# context
contextは、特定の条件を指定してグループを分けます。
使用方法はdescribeと同じですが、describeには何についてのテストなのかを指定するのに対し、contextには特定の条件を指定します。
contextを用いて条件を指定し、テストコードを2つのグループに分けましょう。
以下のように記述してください。
~~~spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  before do
    @user = FactoryBot.build(:user)
  end

  describe 'ユーザー新規登録' do
    context '新規登録できるとき' do
      it 'nicknameとemail、passwordとpassword_confirmationが存在すれば登録できる' do
      end
    end
    context '新規登録できないとき' do
      it 'nicknameが空では登録できない' do
      end
      it 'emailが空では登録できない' do
      end
      it 'passwordが空では登録できない' do
      end
      it 'passwordとpassword_confirmationが不一致では登録できない' do
      end
      it 'nicknameが7文字以上では登録できない' do
      end
      it '重複したemailが存在する場合は登録できない' do
      end
      it 'emailは@を含まないと登録できない' do
      end
      it 'passwordが5文字以下では登録できない' do
      end
      it 'passwordが129文字以上では登録できない' do
      end
    end
  end
end
~~~