[Ruby] RSpec Email Uniqueness Test Email has already been taken Issues and Workarounds

2 minute read

Problem

When writing a User model test in Rspec

In the test to check the uniqueness of the email By using FactoryBot.create(), if the same test is repeated multiple times, 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 is not rolled back for each test

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

spec/rails_helper.rb


config.use_transactional_fixtures = true

Resolution

1) Initialize the DB for each test

rails db:migrate:reset RAILS_ENV=test

Test succeeded 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 "In case of duplicate emails" 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) has been rewritten to create only when needed

There seems to be a more essential solution, using a gem, For the time being, we have been able to avoid the problem with the method of 2)

Reference

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

Tips: class definition for factory

In addition, FactoryBot.create(:existed_user) and When using FactoryBot.build(:existed_user) You have to specify class: User in FactoryBot.define Encountered the error below

NameError: uninitialized constant ExistedUser

There is no problem with FactoryBot.create(:user) or FactoryBot.build(:user). It seems that it is judged to be User class arbitrarily

First RSpec Result I wrote a test of User model as follows

require'rails_helper'

describe User do
  describe "#create" do
    it "You can register if username, email, password, 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 "Unable to register if user name does not exist" do
        let(:username) {""}
        it {is_expected.to be_invalid}
      end
      context "What can be registered when username is 12 characters" do
        let(:username) {"a"*12}
        it {is_expected.to be_valid}
      end
      context "Unable to register if 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 "In case of duplicate emails" 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 "if password does not match pasword_confirmation" do
        let(:password) {"password_a"}
        let(:password_confirmation) {"password_b"}
        it {is_expected.to be_invalid}
      end
      context "password is 7 characters" do
        let(:password) {"a"*7}
        let(:password_confirmation) {"a"*7}
        it {is_expected.to be_invalid}
      end
      context "when password is 8 characters" do
        let(:password) {"a"*8}
        let(:password_confirmation) {"a"*8}
        it {is_expected.to be_valid}
      end
    end
  end
end