1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-07-17

問題

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

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

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

原因

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

spec/rails_helper.rbには以下の設定済み(デフォルト)

spec/rails_helper.rbあ
config.use_transactional_fixtures = true

解決

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

rails db:migrate:reset RAILS_ENV=test

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

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

spec/factories/users.rb

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

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

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モデルのテストを書いてみました

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
1
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?