【rails】RSpec,factory_botを用いてテストを行う

### factory_bot_rails導入

gem factory_bot_railsを、RSpecと同じ環境に追加する。

```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'
  gem 'factory_bot_rails'
end
```

```console:terminal
$ bundle install
```

ディレクトリ spec/factories を作成する。
ファイル users.rbを作成する。
テストで使うuserクラスのデフォルトインスタンスを定義する。
※テストの際に、一部を変更して使う。

```ruby:spec/factories/users.rb
FactoryBot.define do
  factory :user do
    nickname                    {"abe"}
    email                       {"kkk@gmail.com"}
    password                    {"00000000"}
    password_confirmation       {"00000000"}
  end
end
```

### メソッド

##### buildメソッド
factory_botに記述したクラスをシンボル型で引数に指定してインスタンスを生成します。

```ruby:
#factory_botを利用しない場合
user = User.new(nickname: "abe", email: "kkk@gmail.com", password: "00000000", password_confirmation: "00000000")
#factory_botを利用する場合
user = FacrtoryBot.build(:user)
```
##### createメソッド
buildメソッド同様、factory_botに記述したクラスをシンボル型で引数に指定してインスタンスを生成します。
さらにテスト用のDBに値が保存されます。
テストが終了するたびにDBの内容はロールバックされます。

```ruby:
# createしたインスタンスはDBに保存される
user = FactoryBot.create(:user)
```

### factory_botの記法の省略

インスタンスを作成する際に必要となるレシーバであるクラスの```FactoryBot```という記述を、```rails_helper.rb```に記述することで```user_spec.rb```での記述を省略することができます。

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

##### factory_botを利用したインスタンス生成

```ruby:spec/model/user_spec.rb
require 'rails_helper'
describe User do
  describe '#create' do
    it "nicknameがない場合は登録できないこと" do
      # user = User.new(nickname: "", email: "kkk@gmail.com",password: "00000000", password_confirmation: "00000000")
      user = build(:user, nickname: "")
      user.valid?
      expect(user.errors[:nickname]).to include("can't be blank")
    end

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

### バリデーションに関するテストコードを書く

###### テスト項目
1.nicknameとemail、passwordとpassword_confirmationが存在すれば登録できること
2.nicknameがない場合は登録できないこと
3.emailがない場合は登録できないこと
4.passwordがない場合は登録できないこと
5.passwordが存在してもpassword_confirmationがない場合は登録できないこと
6.nicknameが7文字以上であれば登録できないこと
7.nicknameが6文字以下では登録できること
8.重複したemailが存在する場合登録できないこと
9.passwordが6文字以上であれば登録できること
10.passwordが5文字以下であれば登録できないこと

##### テストコード

テスト項目に沿って次の通り書く。

```ruby:spec/model/user_spec.rb
require 'rails_helper'
describe User do
  describe '#create' do

    #1
    it "nicknameとemail、passwordとpassword_confirmationが存在すれば登録できること" do
      user = build(:user)
      expect(user).to be_valid
    end

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

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

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

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

    #6
    it "nicknameが7文字以上であれば登録できないこと" do
      user = build(:user, nickname: "abematv")
      user.valid?
      expect(user.errors[:nickname]).to include("is too long (maximum is 6 characters)")
    end

    #7
    it "nicknameが6文字以下では登録できること" do
      user = build(:user, nickname: "abemat")
      expect(user).to be_valid
    end

    #8
    it "重複したemailが存在する場合登録できないこと" do
      user1 = create(:user, email: "testman@test")
      user2 = build(:user, email: "testman@test")
      user2.valid?
      expect(user2.errors[:email]).to include("has already been taken")
    end

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

    #10
    it "passwordが5文字以下であれば登録できないこと" do
      user = build(:user, password: "12345", password_confirmation: "12345")
      user.valid?
      expect(user.errors[:password]).to include("is too short (minimum is 6 characters)")
    end
  end
end
```

##### テスト実行

```console:terminal
$ bundle exec rspec
2020-05-10 19:22:19 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead.

User
  #create
    nicknameとemail、passwordとpassword_confirmationが存在すれば登録できること
    nicknameがない場合は登録できないこと
    emailがない場合は登録できないこと
    passwordがない場合は登録できないこと
    passwordが存在してもpassword_confirmationがない場合は登録できないこと
    nicknameが7文字以上であれば登録できないこと
    nicknameが6文字以下では登録できること
    重複したemailが存在する場合登録できないこと
    passwordが6文字以上であれば登録できること
    passwordが5文字以下であれば登録できないこと

Finished in 0.10159 seconds (files took 1.61 seconds to load)
10 examples, 0 failures

```
表示```0 failures```により、予期した通り動作することを確認できた。

##### be_validマッチャ

expectの引数にしたインスタンスが全てのバリデーションをクリアする場合にパスする。
次の通り書く。

```ruby:spec/model/user_spec.rb
    it "nickname、email、passwordとpassword_confirmationが存在すれば登録できること" do
      user = build(:user)
      expect(user).to be_valid
    end
```

### テストの原則

#####①１example、１エクスペクテーション
２つ以上含めると、どちらのエクスペクテーションでエラーが出たのか判別できない。

#####②期待する結果をわかりやすく書く
it "〜" doの"〜"の部分は、期待する結果を書いておく場所。

#####③起きて欲しいことと起きてほしくないこと両方をテストする
起きて欲しいことをチェックしたら、起きてほしくない場合にどんな結果が起こるかも想定しその通りになるか確かめる。
予期せぬ動作が残るのを防ぐため。

#####④境界値をテストする
6文字以上でバリデーションに引っかかる、という条件の場合は「5文字までは正常」と「6文字以上ならば異常」を確かめるようにする。

#####⑤可読性を考えつつ、適度にDRYにする
テストではわかりやすさを優先する。
その上で「Don't Repeat Yourself」

### 関連リンク

https://github.com/thoughtbot/factory_bot

https://devhints.io/factory_bot

https://tech.grooves.com/entry/2015/04/28/173025