"Email has already been taken" problem and workaround in RSpec's Email uniqueness test

problem

When writing a test for the User model in Rspec

In a test to confirm the uniqueness of email If you use FactoryBot.create (), the same test will be repeated multiple times thereafter. The following error is displayed in the second and subsequent tests

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

Cause

It seems that DB rollback is not done for each test

The following settings have been made in spec / rails_helper.rb (default)

spec/rails_helper.rb


config.use_transactional_fixtures = true

Solution

1) Initialize the DB for each test

rails db:migrate:reset RAILS_ENV=test

After that, the test was successful only once

2) Rewrite the test code

spec/factories/users.rb

FactoryBot.define do
  factory :user, class: User do
    username {"test_user"}
    email {"[email protected]"}
    password {"password"}
    password_confirmation {"password"}
  end
  factory :existed_user, class: User do
    username { "existed_user" }
    email { "[email protected]" }
    password { "password" }
    password_confirmation { "password" }
  end
end

spec/models/user_spec.rb

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

context "If the email is duplicated" 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)Only when necessary in the part ofcreateRewritten to do

There seems to be a more essential solution, such as using gem, So far, the problem can be avoided by the method of 2).

reference

Besides, defining factory sequentially was introduced in several articles. Only 5 things to remember when using FactoryBot-Qiita

Tips: Class definition for factory

Also, FactoryBot.create (: existed_user) and When doing FactoryBot.build (: existed_user) You have to specify class: User in FactoryBot.define Encountered the following error

NameError: uninitialized constant ExistedUser

Since FactoryBot.create (: user) and FactoryBot.build (: user) are fine It seems that it is judged to be the User class without permission

First RSpec Result I wrote a test for the User model as follows

require 'rails_helper'

describe User do
  describe "#create" do
    it "username, email, password,Can be registered if password confiramation exists" do
      user = FactoryBot.build(:user)
      expect(user).to be_valid
    end
    describe "username" do
      subject(:user) { FactoryBot.build(:user, username: username) }
      context "Cannot register if username does not exist" do
        let(:username) { "" }
        it { is_expected.to be_invalid }
      end
      context "Can be registered when username is 12 characters" do
        let(:username) { "a"*12 }
        it { is_expected.to be_valid }
      end
      context "Cannot be registered when username is 13 characters" do
        let(:username) { "a"*13 }
        it { is_expected.to be_invalid }
      end
      context "If the username is duplicated" 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 "If email does not exist" do
        let(:email) { "" }
        it { is_expected.to be_invalid }
      end
      context "If the email is duplicated" 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 "If password does not exist" do
        let(:password) { "" }
        let(:password_confirmation) { "password" }
        it { is_expected.to be_invalid }
      end
      context "If password exists but password confirmation does not exist" do
        let(:password) { "passowrd" }
        let(:password_confirmation) { "" }
        it { is_expected.to be_invalid }
      end
      context "password is password_If it does not match confirmation" do
        let(:password) { "password_a" }
        let(:password_confirmation) { "password_b" }
        it { is_expected.to be_invalid }
      end
      context "If password is 7 characters" do
        let(:password) { "a"*7 }
        let(:password_confirmation) { "a"*7 }
        it { is_expected.to be_invalid }
      end
      context "If password is 8 characters" do
        let(:password) { "a"*8 }
        let(:password_confirmation) { "a"*8 }
        it { is_expected.to be_valid }
      end
    end
  end
end

Recommended Posts

"Email has already been taken" problem and workaround in RSpec's Email uniqueness test
ActiveRecord :: RecordInvalid: Validation failed: Email has already been taken error in RSpec test