Rspec　モデル単体テストコードの実装手順

初学者の備忘録です！間違い等あればご指摘お願いいたします！
参考記事 ▶︎https://qiita.com/jnchito/items/42193d066bd61c740612
　　　　 ▶︎https://qiita.com/namitop/items/bf455f8383181ff6edf3


**前提**
こちらの記事では、Rspecを導入した後の手順を記述しております。

未実装の方はこちらを参考にどうぞ→[Rspecの導入方法](https://qiita.com/yumi1003/items/f222ce82c250e93d1e2f)

# テストコードを記述するファイルの用意

手動ではなく、ターミナルのコマンドで実行します。
あらかじめ雛形を記載してくれているファイルを生成するためです。
今回はUserモデルのテストファイルを作成していきます！
　（Userの部分を任意のモデル名に変更してくださいね）

*ターミナル*

```
% rails g rspec:model user
```

これでファイルが生成されました。確認しましょう！

*ターミナル*

```
create  spec/models/user_spec.rb
```
早速生成されたファイルを開いてみます。こちらのファイルを編集することでテストが実行できます。

*spec/models/user_spec.rb*

```
require 'rails_helper'

RSpec.describe User, type: :model do
  pending "add some examples to (or delete) #{__FILE__}"
end
```

# テストコードの雛形を記述する

まず最初は、テストコード内のグループ分けをします。
どの機能に対して行っているテストなのか明確にし、必要事項を洗い出しましょう！

イメージが沸きやすいようにまず完成形をご覧ください。(長文のため一部抜粋しています)

```
RSpec.describe User, type: :model do
  describe 'ユーザー新規登録' do
    before do
      @user = FactoryBot.build(:user)
    end

    describe 'ユーザー新規登録' do
      context '新規登録できるとき' do
        it 'nickname, email, password, password_confirmationが存在すれば登録できる' do
          expect(@user).to be_valid
        end
            .
            .
            .
      context '新規登録できないとき' do
        it 'nicknameが空では登録できない' do
          @user.nickname = ''
          @user.valid?
          expect(@user.errors.full_messages).to include("Nickname can't be blank")
        end
            .
            .
            .
  end
end
```

上記に記述されている用語について説明していきます。

### **describe**
テストコードのグループ分けを行うメソッド。
「どの機能に対してのテストを行うか」をdescribeでグループ分けし、その中に各テストコードを記述。

```
RSpec.describe User, type: :model do
  describe 'ユーザー新規登録' do
    before do
       .
       .
```

### **it**
itメソッドは、describeメソッド同様に、グループ分けを行うメソッド。
itの場合はより詳細に、「describeメソッドに記述した機能において、どのような状況のテストを行うか」を明記。

```
     context '新規登録できないとき' do
        it 'nicknameが空では登録できない' do
          @user.nickname = ''
          @user.valid?
          expect(@user.errors.full_messages).to include("Nickname can't be blank")
        end
      end
            .
            .
```


### **example**
itで分けたグループのこと。また、itに記述した内容のことを指す場合もある。

### FactoryBot
こちらに関しては少々長くなってしまうので、別記事にて説明いたします。

### 注意点
**it・do・end**の3兄弟を忘れずに記述してください！
記述漏れがあるとターミナル上でテストが正しく通らないことがありました...。
▶︎[PENDINGで動揺した話](https://qiita.com/yumi1003/items/db2fc00b8dffc2e8dba5)

## さらに細かく見ていく!
ここでは新規登録が**できないとき**のやり方を紹介します。
下記の記述部分に注目してください。メソッドの詳細を記述していきます。

```
context '新規登録できないとき' do
        it 'nicknameが空では登録できない' do
          @user.nickname = ''
          @user.valid?
          expect(@user.errors.full_messages).to include("Nickname can't be blank")
        end
      end
```

**valid?**
valid?は、バリデーションを実行させて、エラーがあるかどうかを判断するメソッド。
エラーがない場合はtrueを、ある場合はfalseを返す。
エラーがあると判断された場合は、エラーの内容を示すエラーメッセージを生成。

**expection**
エクスペクテーションとは、検証で得られた挙動が想定通りなのかを確認する構文のこと。
**expect().to matcher()**を雛形に、テストの内容に応じて引数や**matcher**を変えて記述。

**matcher**
matcherは、「expectの引数」と「想定した挙動」が一致しているかどうかを判断する。
expectの引数には検証で得られた実際の挙動を指定し、マッチャには、どのような挙動を想定しているかを記述。

**include**
includeは、「expectの引数」に「includeの引数」が含まれていることを確認するマッチャ。

**eq**
eqは、「expectの引数」と「eqの引数」が等しいことを確認するマッチャ。

**errors**
errorsは、インスタンスにエラーを示す情報がある場合、その内容を返すメソッド。
確認には**rails c**を使用。

*ターミナル*

```
% rails c
[1] pry(main)> user = User.new(nickname: '', mail: 'test@example', password: '000000', password_confirmation: '000000')
[2] pry(main)> user.valid?
User Exists? (10.4ms)  SELECT 1 AS one FROM `users` WHERE `users`.`email` = BINARY 'test@example' LIMIT 1
=> false
[3] pry(main)> user.errors
=> #<ActiveModel::Errors:0x00007feb3e0120a8
 @base=
  #<User id: nil, email: "test@example", created_at: nil, updated_at: nil, nickname: "">,
 @details={:nickname=>[{:error=>:blank}]},
 @messages={:nickname=>["can't be blank"]}>
```

**full_messages**
full_messagesは、エラーの内容から、エラーメッセージを配列として取り出すメソッド。
ターミナルでuser.errors.full_messagesと実行すると、以下のようにエラーの内容の配列が返される。
上記の例に続けて、下記のコマンドを実行しましょう！

ターミナル

```
[4] pry(main)> user.errors.full_messages
=> ["Nickname can't be blank"]
```

これで最初に紹介したようなテストコードの一例が出来上がったと思います。

# テストコードの実行
テストの記述できたら、テストコードを実行しましょう！
結果が緑色になっていれば成功です！
赤になっている場合は随時エラー文の確認をし、修正の必要があります。


完成形はこちらになります。(あくまで一例です)

```
require 'rails_helper'

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

    describe 'ユーザー新規登録' do
      context '新規登録できるとき' do
        it 'nickname, email, password, password_confirmationが存在すれば登録できる' do
          expect(@user).to be_valid
        end
        it 'emailは、@が入っていれば登録できる' do
          @user.email = 'test@example'
          expect(@user).to be_valid
        end
        it 'passwordとpassword_confirmationが6文字以上英字入りであれば登録できる' do
          @user.password = '00000a'
          @user.password_confirmation = '00000a'
          expect(@user).to be_valid
        end
      end
      context '新規登録できないとき' do
        it 'nicknameが空では登録できない' do
          @user.nickname = ''
          @user.valid?
          expect(@user.errors.full_messages).to include("Nickname can't be blank")
        end
        it 'emailが空では登録できない' do
          @user.email = ''
          @user.valid?
          expect(@user.errors.full_messages).to include("Email can't be blank")
        end
        it 'emailは一意性がないと登録できない' do
          @user.save
          another_user = FactoryBot.build(:user)
          another_user.email = @user.email
          another_user.valid?
          expect(another_user.errors.full_messages).to include('Email has already been taken')
        end
        it 'emailは@が含まれていないと登録できない' do
          @user.email = 'test.example'
          @user.valid?
          expect(@user.errors.full_messages).to include('Email is invalid')
        end
        it 'passwordが空では登録できない' do
          @user.password = ''
          @user.valid?
          expect(@user.errors.full_messages).to include("Password can't be blank")
        end
        it 'passwordは6文字以上でないと登録できない' do
          @user.password = '0000a'
          @user.password_confirmation = '0000a'
          @user.valid?
          expect(@user.errors.full_messages).to include('Password is too short (minimum is 6 characters)')
        end
        it 'passwordは半角英数字混合でないと登録できない' do
          @user.password = '000000'
          @user.password_confirmation = '000000'
          @user.valid?
          expect(@user.errors.full_messages).to include('Password is invalid')
        end
        it 'passwordは英語のみでは登録できない' do
          @user.password = 'aaaaaa'
          @user.password_confirmation = 'aaaaaa'
          @user.valid?
          expect(@user.errors.full_messages).to include('Password is invalid')
        end
        it 'passwordは全角では登録できない' do
          @user.password = 'ああああああ'
          @user.password_confirmation = 'ああああああ'
          @user.valid?
          expect(@user.errors.full_messages).to include('Password is invalid')
        end
        it 'passwordは確認も含めて2回入力しないと登録できない' do
          @user.password = ''
          @user.password_confirmation = ''
          @user.valid?
          expect(@user.errors.full_messages).to include("Password can't be blank")
        end
        it 'passwordと確認用passwordが一致していないと登録できない' do
          @user.password = '00000a'
          @user.password_confirmation = '00000b'
          @user.valid?
          expect(@user.errors.full_messages).to include("Password confirmation doesn't match Password")
        end
      end
    end
  end
end
```

個人的には最初からガッツリコードを書くのではなく、
**まずexampleを洗い出し全て記述　→　テストコード実行
it~end内の記述　→　テストコードの実行**
としていくと確実ですし、修正もしやすいと感じます。
お疲れ様でした！