RSpecのEmail一意性テストで"Email has already been taken"問題と回避策

# 問題

RspecでUserモデルのテストを書いている際

emailの一意性を確認するテストで
`FactoryBot.create()`を使うと、以降同じテストを複数回繰り返した場合に
2回目以降のテストで以下のエラーが表示される

```
ActiveRecord::RecordInvalid:
  Validation failed: Email has already been taken
```



# 原因

テストごとにDBのロールバックが行われていないと思われる

`spec/rails_helper.rb`には以下の設定済み（デフォルト）

```ruby:spec/rails_helper.rbあ
config.use_transactional_fixtures = true
```



# 解決

## 1) テストごとにDBを初期化

```
rails db:migrate:reset RAILS_ENV=test
```

以後一回に限りテストは成功

## 2)テストコードを書き換える

`spec/factories/users.rb`

```ruby
FactoryBot.define do
  factory :user, class: User do
    username {"test_user"}
    email {"test_user@mail.com"}
    password {"password"}
    password_confirmation {"password"}
  end
  factory :existed_user, class: User do
    username { "existed_user" }
    email { "existed_user@email.com" }
    password { "password" }
    password_confirmation { "password" }
  end
end
```

`spec/models/user_spec.rb`

```ruby
subject(:user) { FactoryBot.build(:user, email: email) }

context "emailが重複してする場合" do
  existed_user =  User.find_by(username: "existed_user") || FactoryBot.create(:existed_user)
  let(:email) { existed_user.email }
  it { is_expected.to be_invalid }
end
```



`User.find_by(username: "existed_user") || FactoryBot.create(:existed_user)`の部分で必要な場合だけ`create`するように書き換えました

gemを使ったり他にもっと本質的な解決手段がありそうですが、
今のところ2)の方法で問題回避できております

# 参考
他に、factoryをシーケンシャルに定義するというのはいくつかの記事で紹介されていました
[FactoryBotを使う時に覚えておきたい、たった5つのこと - Qiita](https://qiita.com/luigiii/items/32717b6c382272e2134e)

# Tips: factoryにはclass定義を

また、`FactoryBot.create(:existed_user)`や
`FactoryBot.build(:existed_user)`とするときに
`FactoryBot.define`のなかで`class: User`を指定してやらないと
以下のエラーに遭遇

```
NameError: uninitialized constant ExistedUser
```

`FactoryBot.create(:user)`や`FactoryBot.build(:user)`は問題ないので
勝手にUserクラスだと判断しているものと思われる


初RSpec
結果以下のようにUserモデルのテストを書いてみました

```ruby
require 'rails_helper'

describe User do
  describe "#create" do
    it "username, email, password, password confiramationが存在すれば登録できること" do
      user = FactoryBot.build(:user)
      expect(user).to be_valid
    end
    describe "username" do
      subject(:user) { FactoryBot.build(:user, username: username) }
      context "usernameが存在しない場合登録できないこと" do
        let(:username) { "" }
        it { is_expected.to be_invalid }
      end
      context "usernameが12文字の場合登録できること" do
        let(:username) { "a"*12 }
        it { is_expected.to be_valid }
      end
      context "usernameが13文字の場合登録できないこと" do
        let(:username) { "a"*13 }
        it { is_expected.to be_invalid }
      end
      context "usernameが重複している場合" do
        existed_user =  User.find_by(username: "existed_user") || FactoryBot.create(:existed_user)
        let(:username) { existed_user.username }
      end
    end
    describe "email" do
      subject(:user) { FactoryBot.build(:user, email: email) }
      context "emailが存在しない場合" do
        let(:email) { "" }
        it { is_expected.to be_invalid }
      end
      context "emailが重複してする場合" do
        existed_user =  User.find_by(username: "existed_user") || FactoryBot.create(:existed_user)
        let(:email) { existed_user.email }
        it { is_expected.to be_invalid }
      end
    end
    describe "password" do
      subject(:user) { FactoryBot.build(:user, password: password, password_confirmation: password_confirmation) }
      context "passwordが存在しない場合" do
        let(:password) { "" }
        let(:password_confirmation) { "password" }
        it { is_expected.to be_invalid }
      end
      context "passwordが存在しても、password confirmationが存在しない場合" do
        let(:password) { "passowrd" }
        let(:password_confirmation) { "" }
        it { is_expected.to be_invalid }
      end
      context "passwordがpasword_confirmationと一致しない場合" do
        let(:password) { "password_a" }
        let(:password_confirmation) { "password_b" }
        it { is_expected.to be_invalid }
      end
      context "passwordが7文字の場合" do
        let(:password) { "a"*7 }
        let(:password_confirmation) { "a"*7 }
        it { is_expected.to be_invalid }
      end
      context "passwordが8文字の場合" do
        let(:password) { "a"*8 }
        let(:password_confirmation) { "a"*8 }
        it { is_expected.to be_valid }
      end
    end
  end
end
```