Techioz Blog

Ruby、rspec によるファクトリーボットエラーの重複

概要

アプリでエラーが発生してしまいました。最初に RSpec と PagesController テストでテストしています。

let(:user) { create(:user, favorite_category: "Elettronica") }

エラーが発生します:

Failure/Error:
  factory :user , class: User do
    email { '[email protected]' }
    password { 'password' }
    full_name { 'a b' }
    uid { '12345' }
    avatar_url { 'http://example.com/avatar.png' }
    role { 0 }
    favorite_category { "moda" }
  end

フォルダー spec/factory 内の異なるファイルに 3 つのファクトリーがあり、すべてユーザーに関連しています。

行き詰まってしまい、何が問題なのか分かりません。

私のユーザーモデル:

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and 
   :omniauthable
    devise :omniauthable, :database_authenticatable, :registerable,
     :recoverable, :rememberable, :validatable, omniauth_providers: 
    [:google_oauth2]
    
attr_accessor :current_password

has_one_attached :avatar

def avatar_thumbnail
  if avatar.attached?
  avatar.variant(resize: "150x150!").processed
 else
  "/assets/default_profile.jpg"
 end  
end

has_many :messages
has_many :reviews
has_many :researches
has_many :active_relationships, class_name: "Relationship",
                            foreign_key: "follower_id",
                            dependent: :destroy
has_many :following, through: :active_relationships, source: 
:followed    
has_many :passive_relationships, class_name: "Relationship",
                             foreign_key: "followed_id",
                             dependent: :destroy
has_many :followers, through: :passive_relationships, source: 
:follower


def self.from_omniauth(auth)
   where(provider: auth.provider, uid: auth.uid).first_or_create do 
   |user|
   user.email = auth.info.email
   user.password = Devise.friendly_token[0, 20]
   user.full_name = auth.info.name # assuming the user model has a 
   name
   user.avatar_url = auth.info.image # assuming the user model has an 
   image
  # If you are using confirmable and the provider(s) you use validate 
  emails,
  # uncomment the line below to skip the confirmation emails.
  # user.skip_confirmation!
  end
  end

def admin?
 role == 1  # Supponiamo che tu abbia un campo 'role' nel tuo modello 
 User
end

def moderator?
 role == 2  # Supponiamo che tu abbia un campo 'role' nel tuo modello 
 User
end
**strong text**
def follow(other_user)
 active_relationships.create(followed_id: other_user.id)
end


# Unfollows a user.
def unfollow(other_user)
 active_relationships.find_by(followed_id: other_user.id).destroy
 end

# Returns true if the current user is following the other user.
def following?(other_user)
  following.include?(other_user)
end
end`

ファクトリーユーザー

FactoryBot.define do
 factory :user do
  email { '[email protected]' }
  password { 'password' }
  full_name { 'John Doe' }
  uid { '12345' }
  avatar_url { 'http://example.com/avatar.png' }
  role { 0 }
  favorite_category { "Elettronica" }
end
end

ページ仕様:

require 'rails_helper'
require "./spec/factories/user.rb"

RSpec.describe PagesController, type: :controller do

let(:user) { create(:user, favorite_category: "Elettronica") }

describe "GET #home" do
it "returns a successful response" do
  get :home
  expect(response).to be_successful
end

it "assigns the current user" do
  get :home
  expect(assigns(:user)).to eq(user)
end

it "assigns search results if the user has a favorite category" do
  get :home
  expect(assigns(:search_results)).not_to be_nil
end

it "does not assign search results if the user has no favorite category" 
do
  user.update(favorite_category: "nessuna categoria")
  get :home
  expect(assigns(:search_results)).to be_nil
end
end

describe 'GET #search' do
 it 'assigns instance variables and renders the template' do
  get :search, params: {
    keyword: 'lamp',
    sort_order: 'PricePlusShippingLowest',
    minprice: 0,
    maxprice: 100,
    instaexp: 1,
    place: 'US',
    minf: '0',
    maxtime: '10'
  }

  expect(assigns(:users)).to be_a(ActiveRecord::Relation)
  expect(assigns(:keyword)).to eq('lamp')
  expect(assigns(:sort_order)).to eq('PricePlusShippingLowest')
  expect(assigns(:minprice)).to eq(0)
  expect(assigns(:maxprice)).to eq(100)
  expect(assigns(:instaexp)).to eq(1)
  expect(assigns(:place)).to eq('US')
  expect(assigns(:minf)).to eq('0')
  expect(assigns(:maxtime)).to eq('10')

  # Add more expectations for other instance variables as needed

  expect(response).to render_template(:search)
  end

  end

describe 'GET #search' do
 it 'assigns a non-empty @search_results' do
  # Stub the external service that is being called in the controller
  allow_any_instance_of(Caller).to receive(:cerca).and_return([
    { 'title' => 'Item 1', 'price' => 10 }
  ])

  get :search, params: {
    keyword: 'lamp',
    sort_order: 'PricePlusShippingLowest',
    minprice: 0,
    maxprice: 100,
    instaexp: 1,
    place: 'US',
    minf: '0',
    maxtime: '10'
  }

  expect(assigns(:search_results)).not_to be_nil
end
end
end

エラーは FactoryBot::DuplicateDefinitonError: 工場出荷時に登録済み:ユーザー

解決策

エラーメッセージ全体を共有しましたね。したがって、ほとんどのアプリケーションでは電子メールは一意である必要があるため、電子メール列で重複エラーが発生していると推測することしかできません。

ファクトリでは、次の行でユーザーが作成されるたびに同じ電子メール アドレスが作成されます。

email { '[email protected]' }

つまり、一度に作成できるレコードは 1 つだけです。ただし、一部のテストでは 2 人のユーザーを作成する必要があり、そのようなテストは常に失敗します。

FactoryBot を使用すると、一意である必要がある列に連続した値を作成できます。電子メールの定義を次のように変更することで、FactoryBot でシーケンスを有効にできます。

sequence :email { |n| "test-#{n}@example.com" }

これにより、メール アドレスの数字が増加することで、異なるメール アドレスを持つすべてのユーザーが生成されます: [email protected]、[email protected]、[email protected]…