【Rails】単体テストコードの実装

# 目的
Railsで作成したアプリにテストコードを実装する。

# 開発環境
macOS: *Big Sur*
Rubyバージョン: *2.6.5*
Railsバージョン: *6.0.0*

# 前提
- アプリを作成済み。[【Rails】簡単な投稿アプリの作成](https://qiita.com/oak1331/items/18a4f3a3386aab0fa354)
- `devise`を導入済み。[【Rails】ユーザー管理機能(devise)の導入](https://qiita.com/oak1331/items/0759bd297e9fd3b81ed2)
- アプリを日本語化対応済み。[【Rails】Railsの日本語化](https://qiita.com/oak1331/items/563e6ae4786ef61f2687)
- `FactoryBot`及び`Faker`を導入済み。[FactoryBot・Fakerの導入](https://qiita.com/oak1331/items/0d2b7b3638d276c20d18)

# 手順

1. [はじめに](#はじめに)
1. [RSpecの導入](#rspecの導入)
1. [RSpecの設定](#rspecの設定)
1. [ファイルの用意](#ファイルの用意)
1. [モデル単体テストの実装](#モデル単体テストの実装)
1. [テストコードの実行](#テストコードの実行)

# はじめに

今回は単体テストコードの実装を行っていきます。
テストコードを記述するメリットとして以下のようなメリットが考えられます。

- 人為的ミスのリスク回避。
- 作業効率化。
- どのように確かめたか記録を残す。

また、テストコードは`正常系`と`異常系`に分けられ、
正常系は`「ユーザーが開発者の意図する操作を行った時の挙動」`を確認するテストコード、
異常系は`「ユーザーが開発者の意図しない操作を行った時の挙動」`を確認するテストコードと分類されます。

# RSpecの導入

それでは早速始めていきます！
まず、Railsのテストコードを書くために`RSpec`というGemをインストールします。

```ruby:Gemfile
group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'rspec-rails', '~> 4.0.0'
end
```

この時、`group :development, :test`というグループの中に記述しまします。
そうすることで、Gemの動作に制限をもたせます。

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

これでインストールできました！

# RSpecの設定

次にアプリ内で使用するための設定をしていきます。

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

インストールが完了すると、ディレクトリやファイルが生成されます。
続いて生成された`.rspec`ファイルを開き、テストコードの結果をターミナル上に可視化するため、以下のように記述します。

```:.rspec
--format documentation
```

これでRSpecの設定は完了です！

# ファイルの用意

テストコードを記述するファイルを用意します。
今回はUserモデルのテストコードを実装するため、下記コマンドにてテストファイルを生成しましょう!

```:ターミナル
% rails g rspec:model user
```

これで、事前準備は完了です!

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

次にテストコードを実装していきます!

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

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

    context '登録ができる時' do
      it '全ての項目の入力がが存在すれば登録できること' do
        expect(@user).to be_valid
      end

      it 'passwordが6文字以上かつ半角英数字混合であれば登録できること' do
        @user.password = '12345a'
        @user.password_confirmation = '12345a'
        expect(@user).to be_valid
      end
    end

    context '登録ができない時' do
      it 'nicknameが空では登録できないこと' do
        @user.nickname = ''
        @user.valid?
        expect(@user.errors.full_messages).to include("ニックネームを入力してください")
      end

      it 'emailが空では登録できないこと' do
        @user.email = ''
        @user.valid?
        expect(@user.errors.full_messages).to include("メールアドレスが入力されていません。")
      end

      it '重複したemailが存在する場合登録できないこと' do
        @user.save
        another_user = FactoryBot.build(:user, email: @user.email)
        another_user.valid?
        expect(another_user.errors.full_messages).to include("メールアドレスは既に使用されています。")
      end

      it 'passwordが空では登録できないこと' do
        @user.password = ''
        @user.valid?
        expect(@user.errors.full_messages).to include("パスワードが入力されていません。", "もう一度パスワードを入力してくださいとパスワードの入力が一致しません")
      end

      it 'passwordが5文字以下であれば登録できないこと' do
        @user.password = '1234a'
        @user.password_confirmation = '1234a'
        @user.valid?
        expect(@user.errors.full_messages).to include("パスワードは6文字以上に設定して下さい。")
      end

      it 'passwordが半角数字のみのときに登録できないこと' do
        @user.password = '123456'
        @user.password_confirmation = '123456'
        @user.valid?
        expect(@user.errors.full_messages).to include("パスワードは有効でありません。")
      end

      it 'passwordが半角英語のみのときに登録できないこと' do
        @user.password = 'abcdef'
        @user.password_confirmation = 'abcdef'
        @user.valid?
        expect(@user.errors.full_messages).to include("パスワードは有効でありません。")
      end

      it 'passwordが全角のときに登録できないこと' do
        @user.password = '１２３４５A'
        @user.password_confirmation = '１２３４５A'
        @user.valid?
        expect(@user.errors.full_messages).to include("パスワードは有効でありません。")
      end

      it 'passwordとpassword_confirmationが不一致では登録できないこと' do
        @user.password = '12345a'
        @user.password_confirmation = '123456a'
        @user.valid?
        expect(@user.errors.full_messages).to include("もう一度パスワードを入力してくださいとパスワードの入力が一致しません")
      end
    end
  end
end
```

それでは順番に説明していきます！

```ruby:spec/models/user_spec.rb
  describe '#create' do
```

この`describeメソッド`は、テストコードのグループ分けを行っています。
`どの機能に対してのテストを行うか`をdescribeでグループ分けし、その中に各テストコードを記述します。
テストコードが膨大になると解読が大変になるので、付けておきます！

```ruby:spec/models/user_spec.rb
  #describe '#create' do
    it 'nicknameが空では登録できないこと' do
```

次にこの`itメソッド`ですが、describeメソッド同様に、グループ分けを行っています。
itの場合はより詳細に、`describeメソッドに記述した機能において、どのような状況のテストを行うか`を明記します。

```ruby:spec/models/user_spec.rb
  @user.valid?
```

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

# テストコードの実行

テストコードが記述できたらテストの実行をします。

```:ターミナル
% bundle exec rspec spec/models/user_spec.rb
```

`bundle exec`でGemの依存関係を整理しています。

テストを実行し、下記画像のように成功すれば実装完了です！

![User単体テスト.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1337030/fdac0d5c-cac0-b137-4632-b9e2d959b220.png)

# 最後に

以上で単体テストコードの実装は完了です。
では。