【Ruby on Rails】モデルの単体テストコードについてまとめ(RSpec)

初学者です。
テストコードがなぜか大好きです。

今回はモデルの単体テストコードについてまとめます。
私はRSpecを利用しています。

モデルのテストコードを書く方針は
インスタンスを生成し、そのインスタンスがモデルに規定したどおりの挙動になるか（バリデーションが正しく働くか等）を確かめる
ことです。


## 前提条件

- pry-railsを導入済みである
- FactoryBotを導入済みである

上記については以下の記事にまとめています。

[【Ruby on Rails】デバッグツール(pry-rails)](https://qiita.com/mumucochimu/items/7eb477077c493822977c)
[【Ruby on Rails】FactoryBotとFakerについてまとめ](https://qiita.com/mumucochimu/items/10432fd5173e63ebd418)


## RSpec

RSpecは、Railsのテストコードを書くために用いられるGemです。
[RSpec公式Github](https://github.com/rspec/rspec-rails)


## テストコードの種類

- ### 単体テストコード
モデルやコントローラーなど`機能ごと`に問題がないか確認します。
例えばバリデーションがきちんと動作しているかなどです。


- ### 結合テストコード
ユーザーがブラウザで操作する`一連の流れ`を再現して問題がないか確かめます。
例えば、ユーザー登録で「名前とメールアドレスとパスワードを入力するとトップページに遷移して表示がユーザーの名前に変わっている」などの一連の動作を確認します。


- ### 正常系
`ユーザーが開発者の意図する操作を行った時の挙動`を確認するテストコードです。
例えば、ユーザー登録で問題なく全てのデータが入力された場合などです。

- ### 異常系
`ユーザーが開発者の意図しない操作を行った時の挙動`を確認するテストコードです。
例えば、ユーザー登録で正しい値が入っていないと登録できないかどうかなどです。


## RSpec導入

下記のように`Gemfile`の`group :development, :test do`に`gem 'rspec-rails', '~> 4.0.0'`と記述します。

```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
```

ターミナルで下記のコマンドを実行しGemを導入します。

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

ターミナルで下記のコマンドを実行しアプリケーション内でRSpecを使用するための設定を行います。実行すると関連するファイルが生成されます。

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

生成された`.rspec`に下記のように記述します。
テストコードの結果をターミナルで可視化するために必要な記述です。

```Ruby:.rspec
--require spec_helper

# 以下を追加
--format documentation
```

ターミナルで下記のコマンドを実行しテストコードを記述するファイルを生成します。

```Ruby:ターミナル
rails g rspec:model モデル名

# 以下はuserモデルのファイル生成の例
rails g rspec:model user
```

テストコードを記述するファイルは手動で生成してもいいのですが、`require 'rails_helper'`の記述がないと読み込めないファイルがあり、きちんとテストできないのでコマンドで生成した方がいいみたいです。

もし、このコマンドで生成した際に既にFactoryBotが導入済みであればFactoryBotのファイルも一緒に生成してくれますし楽だと思います。


## 単体テストコードの準備

本題です。
まだ業務経験もないペーペーなのであしからず。

ユーザー登録についての単体テストコードを例にします。

最初に`何を検証すべきなのか`を洗い出していきます。

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

RSpec.describe User, type: :model do

# FactoryBotでデータをbuild
  before do
    @user = FactoryBot.build(:user)
  end

  describe 'ユーザー新規登録' do

# 正常系
    context '新規登録できる時' do
      it '全ての項目が存在すれば登録できる' do
      end
      it 'nameが6文字以下であれば登録できる' do
      end
      it 'passwordとpassword_confirmationが6文字以上であれば登録できる' do
      end
    end

# 異常系
    context '新規登録できない時' do
      it 'nameが空だと登録できない' do
      end
      it 'emailが空だと登録できない' do
      end
      it 'passwordが空だと登録できない' do
      end
      it 'passwordが存在してもpassword_confirmationが空だと登録できない' do
      end
      it 'nameが7文字以上では登録できない' do
      end
      it '重複したemailが存在する場合登録できない' do
      end
      it 'passwordが5文字以下では登録できない' do
      end
    end
  end
end
```


解説していきます。

まず以下のように`before do`でFactoryBotのデータをbuildします。
インスタンス変数にする必要があるので`@user`になります。

```Ruby:
before do
    @user = FactoryBot.build(:user)
end
```


次に以下のように`describe`で`どの機能についてのテストを行うか`を記述します。
グループ分けのようなものです。

```Ruby:
describe 'ユーザー新規登録' do

end
```

次に以下のように`context`でさらに分けていきます。
contextでは`どんな状況を確認したいのか`で分けるので、私は正常系と異常系に分けて考えます。

```Ruby:
# 正常系
context '新規登録できる時' do

end

# 異常系
context '新規登録できない時' do

end
```


次に以下のように`it`でさらに細かい機能に分けます。
itに書いた`確認したい状況`をテストしていきます。

```Ruby:
it '全ての項目が存在すれば登録できる' do

end
```


## 正常系

正常系を書いていきます。

```Ruby:spec/models/user_spec.rb
context '新規登録できる時' do

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

  it 'nameが6文字以下であれば登録できる' do
    @user.name = 'abcde'
    expect(@user).to be_valid
  end

  it 'passwordとpassword_confirmationが6文字以上であれば登録できる' do
    @user.password = '123456'
    @user.password_confirmation = '123456'
    expect(@user).to be_valid
  end
end
```

解説します。

まず「全ての項目が存在すれば登録できる」についてです。
`expect(@user)`で`@user`が`be_valid`で正しいかどうかを判断しています。

```Ruby:
expect(@user).to be_valid
```

次は「nameが6文字以下であれば登録できる」についてです。
`@user.name`に`abcde(つまり6文字以下の文字列)`を代入して、そのデータが入った@userを`be_valid`で正しいかどうかを判断しています。

```Ruby:
@user.name = 'abcde'
@expect(@user).to be_valid
```

次に「passwordとpassword_confirmationが6文字以上であれば登録できる」についてです。
nameと同じように`@user.password`に`123456`を代入し、そのデータが入った@userを`be_valid`で正しいかどうかを判断しています。

```Ruby:
@user.password = '123456'
@user.password_confirmation = '123456'
expect(@user).to be_valid
```

これで成功するかどうかターミナルに以下を入力します。

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

成功したら緑色の文字で結果が出力されます。


## 異常系

1つを例に解説します。
「nameが空だと登録できない」についてを例にします。

```Ruby:
it 'nameが空だと登録できない' do

  # 下記を追加
  @user.name = ''
  @user.valid?
  binding.pry

end
```

まずFactoryBotでデータを入れてある@userのnameに「''」のように空を代入します。
次に`valid?`で正しいか？（trueかfalseを返す)を確認しています。

そして`binding.pry`で処理を止める記述をしました。
この状態でターミナルに以下を入力すると処理が止まります。

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

処理が止まりコンソールに入力できるようになるので`user.errors.full_messages`と入力するとメッセージが出てきます。
そのメッセージを踏まえてテストコードを変更していきます。

```Ruby:
it 'nameが空だと登録できない' do

  @user.name = ''
  @user.valid?

# 以下を変更
  expect(@user.errors.full_messages).to include("name can't be blank")

end
```

上記の記述は`expect`の引数に`include`の引数が含まれるという意味です。
先ほど`user.errors.full_messages`で出てきたメッセージをincludeの引数に与えることで一致する(含まれる)ということになります。


こんな感じで記述していきます。

以上です。