44
36

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 5 years have passed since last update.

FactoryBot 入門 [翻訳][Railsの場合まとめ]

Last updated at Posted at 2019-09-16

この記事は、factory_botRails の場合だけを翻訳&わかりやすく補足を追加したものになります。

Gemfileを更新する(Update Your Gemfile)

Railsを使用している場合:

gem "factory_bot_rails"

Gemfileが更新されたら、バンドルを更新する必要があります。

bundle install

テストスイートを構成する(Configure your test suite)

RSpec

Railsを使用している場合、次の設定を spec/support/factory_bot.rbに追加し、rails_helper.rbにそのファイルが必要であることを確認してください:

RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end

※テストスイートに FactoryBot::Syntax::Methodsを含めない場合、すべてのfactory_botメソッドの前に FactoryBotを付ける必要があります。

FactoryBot.create(:user)

# OK
create(:user)

ファクトリーの定義(Defining factories)

各ファクトリーには、名前と一連の属性があります。
名前は、デフォルトでオブジェクトのクラスを推測するために使用されます。

# これはUserクラスを推測します
FactoryBot.define do
  factory :user do
    first_name { "John" }
    last_name  { "Doe" }
    admin { false }
  end
end

クラスを明示的に指定することもできます。

# これはUserクラスを使用します(そうでなければ、Adminが推測されます)
factory :admin, class: User do

定数が利用できない場合

(たとえば、モデルのロードを待機するRailsエンジンを使用している場合)
シンボルまたは文字列を渡すこともできます。
オブジェクトの構築を開始すると、factory_botが後で定数化されます。

# Doorkeeper::AccessTokenがまだロードされていなくても問題ありません
factory :access_token, class: "Doorkeeper::AccessToken"

Rubyのブロック構文のため、属性を「ハッシュ」として定義します。
(例、serialized / JSON列には、2組の中括弧が必要です。

factory :program do
  configuration { { auto_resolve: false, auto_define: true } }
end

そのクラスのインスタンスを作成するために必要な属性の最も単純なセットを提供する各クラスに1つのファクトリーを持つことを強くお勧めします。
ActiveRecordオブジェクトを作成している場合、検証によって必要とされ、デフォルトを持たない属性のみを提供する必要があることを意味します。
継承を通じて他のファクトリーを作成して、各クラスの一般的なシナリオをカバーできます。

注:同じ名前の複数のファクトリーを定義しようとすると、エラーが発生します。

ファクトリーはどこでも定義できますが、後で自動的にロードされます。
下記の場所にファクトリーが定義されている場合、 FactoryBot.find_definitionsを呼び出します。

spec/factories/*.rb

ファクトリーの使用方法(Using factories)

factory_bot は、いくつかの異なるビルドをサポートしています。

# 保存されていないUserインスタンスを返します
user = build(:user)

# 保存されたUserインスタンスを返します
user = create(:user)

# Userインスタンスの構築に使用できる属性のハッシュを返します
attrs = attributes_for(:user)

# すべての定義済み属性がスタブアウトされたオブジェクトを返します
stub = build_stubbed(:user)

# 上記のメソッドのいずれかにブロックを渡すと、戻りオブジェクトが生成されます
create(:user) do |user|
  user.posts.create(attributes_for(:post))
end

使用するファクトリーに関係なく、ハッシュを渡すことで定義済みの属性をオーバーライドできます。

# ユーザーインスタンスをビルドし、first_nameプロパティをオーバーライドします
user = build(:user, first_name: "Joe")
user.first_name
# => "Joe"

エイリアス(Aliases)

factory_botを使用すると、既存のファクトリーのエイリアスを定義して、再利用しやすくすることができます。
例えば、Postオブジェクトに実際にUserクラスのインスタンスを参照するauthor属性がある場合に便利です。
通常、factory_botは関連付け名からファクトリー名を推測できますが、この場合、作成者ファクトリーを探しますが無駄です。
そのため、エイリアスネームの下で使用できるように、ユーザーファクトリーにエイリアスを作成します。

factory :user, aliases: [:author, :commenter] do
  first_name { "John" }
  last_name { "Doe" }
  date_of_birth { 18.years.ago }
end

factory :post do
  author
  # instead of
  # association :author, factory: :user
  title { "How to read a book effectively" }
  body { "There are five steps involved." }
end

factory :comment do
  commenter
  # instead of
  # association :commenter, factory: :user
  body { "Great article!" }
end

依存属性(Dependent Attributes)

属性は、エバリュエーターを使用して他の属性の値に基づくことができます
動的属性ブロックに渡されます:

factory :user do
  first_name { "Joe" }
  last_name  { "Blow" }
  email { "#{first_name}.#{last_name}@example.com".downcase }
end

create(:user, last_name: "Doe").email
# => "joe.doe@example.com"

一時的な属性(Transient Attributes)

一時的な属性をファクトリーに渡すことで、DRY原則でコードを書くことができます。

factory :user do
  transient do
    rockstar { true }
    upcased { false }
  end

  name { "John Doe#{" - Rockstar" if rockstar}" }
  email { "#{name.downcase}@example.com" }

  after(:create) do |user, evaluator|
    user.name.upcase! if evaluator.upcased
  end
end

create(:user, upcased: true).name
#=> "JOHN DOE - ROCKSTAR"

一時属性は、モデルに設定し属性が存在する場合でも、上書きしようとする場合でも。
attributes_for内では無視されません。

メソッド名/予約語の属性(Method Name/Reserved Word Attributes)

属性が既存のメソッドまたは予約語(DefinitionProxyクラスのすべてのメソッド)と競合する場合、それらを定義できます。add_attribute

factory :dna do
  add_attribute(:sequence) { 'GATTACA' }
end

factory :payment do
  add_attribute(:method) { 'paypal' }
end

継承(Inheritance)

ファクトリーをネストすることにより、共通の属性を繰り返すことなく、同じクラスに対して複数のファクトリーを簡単に作成できます。

factory :post do
  title { "A title" }

  factory :approved_post do
    approved { true }
  end
end

approved_post = create(:approved_post)
approved_post.title    # => "A title"
approved_post.approved # => true

親モデルを明示的に割り当てることもできます。

factory :post do
  title { "A title" }
end

factory :approved_post, parent: :post do
  approved { true }
end

上記のように、各クラスの基本的なファクトリーを定義することをお勧めします。
作成に必要な属性のみが含まれます。次に、より具体的に作成します。
この基本的な親から継承するファクトリーの定義は、DRY原則になります。

関連付け(Associations)

ファクトリー内で関連付けを設定することは可能です。
ファクトリー名が関連付け名と同じ場合、ファクトリー名は省略できます。

factory :post do
  # ...
  author
end

別のファクトリーを指定するか、属性をオーバーライドすることもできます。

factory :post do
  # ...
  association :author, factory: :user, last_name: "Writely"
end

factory_bot 5では、親オブジェクトへの関連付けはデフォルトで同じビルド戦略を使用します。

FactoryBot.define do
  factory :author

  factory :post do
    author
  end
end

post = build(:post)
post.new_record?        # => true
post.author.new_record? # => true

post = create(:post)
post.new_record?        # => false
post.author.new_record? # => false

これは、以前のバージョンのデフォルトの動作とは異なります
factory_bot のアソシエーションは常に戦略と一致するとは限りません。
以前のバージョンを引き続き使用する場合は、下記の設定をすることで解消できます。
use_parent_strategy設定オプションを falseに設定します。

FactoryBot.use_parent_strategy = false

# ユーザーと投稿を作成して保存します。
post = create(:post)
post.new_record?        # => false
post.author.new_record? # => false

# ユーザーをビルドして保存し、ビルドしますが投稿は保存しません。
post = build(:post)
post.new_record?        # => true
post.author.new_record? # => false

関連付けられたオブジェクトを保存しないようにするには、ファクトリーで strategy::buildを指定します。

FactoryBot.use_parent_strategy = false

factory :post do
  # ...
  association :author, factory: :user, strategy: :build
end

# ユーザーを作成してから、投稿を作成しますが、どちらも保存しません
post = build(:post)
post.new_record?        # => true
post.author.new_record? # => true

strategy::buildオプションはassociationの明示的な呼び出しに渡す必要があることに注意してください。
暗黙の関連付けでは使用できません。

factory :post do
  # ...
  author strategy: :build # <<<これは動作しません。author_idがnilになります。

「has_many」関係のデータの生成はもう少し複雑です。
必要な柔軟性の量に応じて、しかし関連データの生成で確実な例があります。

FactoryBot.define do

  # ユーザーの `belongs_to`アソシエーションでファクトリーをポストする
  factory :post do
    title { "Through the Looking Glass" }
    user
  end

  # 投稿が関連付けられていないユーザーファクトリー
  factory :user do
    name { "John Doe" }

    # user_with_postsは、ユーザーの作成後に投稿データを作成します
    factory :user_with_posts do
      # posts_countは一時的な属性として宣言されており、
      # ファクトリーの属性、およびエバリュエーターを介したコールバック
      transient do
        posts_count { 5 }
      end

      # after(:create)は2つの値を生成します。ユーザーインスタンス自体と
      # 一時的なものを含む、工場からのすべての値を保存するエバリュエーター
      # 属性; `create_list`の2番目の引数はレコード数です
      # 作成し、ユーザーが投稿に適切に関連付けられていることを確認します
      after(:create) do |user, evaluator|
        create_list(:post, evaluator.posts_count, user: user)
      end
    end
  end
end

これにより、下記のことが可能になります。

create(:user).posts.length # 0
create(:user_with_posts).posts.length # 5
create(:user_with_posts, posts_count: 15).posts.length # 15

「has_and_belongs_to_many」関係のデータの生成は非常に似ています。
上記の「has_many」関係に、わずかな変更を加えて、単一ではなく、
モデルの複数形の属性名に対するオブジェクトの配列を生成します。

以下は、単一バージョンの属性名のオブジェクトを介して関連する2つのモデル作成の例です。
has_and_belongs_to_many

FactoryBot.define do

  # プロファイルの「belongs_to」関連付けを持つ言語ファクトリー
  factory :language do
    title { "Through the Looking Glass" }
    profile
  end

  # 言語が関連付けられていないプロファイルファクトリー
  factory :profile do
    name { "John Doe" }

    # profile_with_languagesは、profile が作成された後にlanguagesを作成します。
    factory :profile_with_languages do
      # languages_countは無視される属性として宣言されており、
      # ファクトリーの属性、およびエバリュエーターを介したコールバック
      transient do
        languages_count { 5 }
      end

      # after(:create)は2つの値を生成します。プロファイルインスタンス自体
      # エバリュエーター。ファクトリーを含むすべての値を格納します。
      # 属性を無視しました。 `create_list`の2番目の引数は、
      # 作成するレコード。プロファイルが適切に関連付けられていることを確認します。
      after(:create) do |profile, evaluator|
        create_list(:language, evaluator.languages_count, profiles: [profile])
      end
    end
  end
end

これにより、下記のことが可能になります。

create(:profile).languages.length # 0
create(:profile_with_languages).languages.length # 5
create(:profile_with_languages, languages_count: 15).languages.length # 15

ポリモーフィックな関連付け

ポリモーフィックな関連付けは、特性を使用して処理できます。

FactoryBot.define do
  factory :video
  factory :photo

  factory :comment do
    for_photo # default to the :for_photo trait if none is specified

    trait :for_video do
      association :commentable, factory: :video
    end

    trait :for_photo do
      association :commentable, factory: :photo
    end
  end
end

これにより、下記のことが可能になります。

create(:comment)
create(:comment, :for_video)
create(:comment, :for_photo)

シーケンス(Sequences)

特定の形式(たとえば、電子メールアドレス)の一意の値はシーケンスを使用して生成されます。

#新しいシーケンスを定義
FactoryBot.define do
  sequence :email do |n|
    "person#{n}@example.com"
  end
end

generate :email
# => "person1@example.com"

generate :email
# => "person2@example.com"

シーケンスは、動的属性で使用できます。

factory :invite do
  invitee { generate(:email) }
end

または暗黙的な属性としてシーケンスを定義すると、シーケンスと同じ名前のファクトリーができます。

factory :user do
  email # `email {generate(:email)}`と同じ
end

また、特定のファクトリーでのみ使用されるインラインシーケンスを定義することもできます。

factory :user do
  sequence(:email) { |n| "person#{n}@example.com" }
end

初期値をオーバーライドすることもできます。

factory :user do
  sequence(:email, 1000) { |n| "person#{n}@example.com" }
end

ブロックがない場合、値はその初期値から始まり、自動的に増加します。

factory :post do
  sequence(:position)
end

シーケンスにはエイリアスも設定できます。シーケンスエイリアスは同じカウンターを共有します。

factory :user do
  sequence(:email, 1000, aliases: [:sender, :receiver]) { |n| "person#{n}@example.com" }
end

# senderおよび:receiverによって共有される:emailの値カウンターを増加します
generate(:sender)

エイリアスを定義し、カウンターにデフォルト値(1)を使用します

factory :user do
  sequence(:email, aliases: [:sender, :receiver]) { |n| "person#{n}@example.com" }
end

値の設定:

factory :user do
  sequence(:email, 'a', aliases: [:sender, :receiver]) { |n| "person#{n}@example.com" }
end

値は #nextメソッドをサポートする必要があります。
ここでは、次の値は「a」、「b」などになります。

シーケンスは FactoryBot.rewind_sequencesで巻き戻すこともできます。
これにより、登録されているすべてのシーケンスが巻き戻されます。

sequence(:email) {|n| "person#{n}@example.com" }

generate(:email) # "person1@example.com"
generate(:email) # "person2@example.com"
generate(:email) # "person3@example.com"

FactoryBot.rewind_sequences

generate(:email) # "person1@example.com"

特性(Traits)

traitを使用すると、どのファクトリーにも属性をグループ化してから適用できます

factory :user, aliases: [:author]

factory :story do
  title { "My awesome story" }
  author

  trait :published do
    published { true }
  end

  trait :unpublished do
    published { false }
  end

  trait :week_long_publishing do
    start_at { 1.week.ago }
    end_at { Time.now }
  end

  trait :month_long_publishing do
    start_at { 1.month.ago }
    end_at { Time.now }
  end

  factory :week_long_published_story,    traits: [:published, :week_long_publishing]
  factory :month_long_published_story,   traits: [:published, :month_long_publishing]
  factory :week_long_unpublished_story,  traits: [:unpublished, :week_long_publishing]
  factory :month_long_unpublished_story, traits: [:unpublished, :month_long_publishing]
end

特性は、暗黙的な属性として使用できます。

factory :week_long_published_story_with_title, parent: :story do
  published
  week_long_publishing
  title { "Publishing that was started at #{start_at}" }
end

注:暗黙的な属性として特性を定義することは機能しないことに注意してください。

同じ属性を定義する特性は、AttributeDefinitionErrorsを発生させません。
最新の属性を定義する特性が優先​​されます。

factory :user do
  name { "Friendly User" }
  login { name }

  trait :male do
    name { "John Doe" }
    gender { "Male" }
    login { "#{name} (M)" }
  end

  trait :female do
    name { "Jane Doe" }
    gender { "Female" }
    login { "#{name} (F)" }
  end

  trait :admin do
    admin { true }
    login { "admin-#{name}" }
  end

  factory :male_admin,   traits: [:male, :admin]   # login will be "admin-John Doe"
  factory :female_admin, traits: [:admin, :female] # login will be "Jane Doe (F)"
end

サブクラスのtraitによって付与された個々の属性をオーバーライドすることもできます。

factory :user do
  name { "Friendly User" }
  login { name }

  trait :male do
    name { "John Doe" }
    gender { "Male" }
    login { "#{name} (M)" }
  end

  factory :brandon do
    male
    name { "Brandon" }
  end
end

factory_botからインスタンスを構築するときに、特性をシンボルのリストとして渡すこともできます。

factory :user do
  name { "Friendly User" }

  trait :male do
    name { "John Doe" }
    gender { "Male" }
  end

  trait :admin do
    admin { true }
  end
end

# 性別が「男性」で名前が「Jon Snow」の管理ユーザーを作成します
create(:user, :admin, :male, name: "Jon Snow")

この機能は、「build」、「build_stubbed」、「attributes_for」、および「create」で機能します。

create_listおよび build_listメソッドもサポートされています。

# 複数のレコード作成
factory :user do
  name { "Friendly User" }

  trait :admin do
    admin { true }
  end
end

# 性別が「男性」で名前が「Jon Snow」の3人の管理ユーザーを作成します
# 2つ目のパラメーターとして作成/ビルドするインスタンスの数(3)
# 複数のレコード作成
create_list(:user, 3, :admin, :male, name: "Jon Snow")

特性は、関連付け(associations)でも簡単に使用できます。

factory :user do
  name { "Friendly User" }

  trait :admin do
    admin { true }
  end
end

factory :post do
  association :user, :admin, name: 'John Doe'
end

# 「John Doe」という名前の管理ユーザーを作成します
create(:post).user

ファクトリーとは異なる関連付け名を使用している場合:

factory :user do
  name { "Friendly User" }

  trait :admin do
    admin { true }
  end
end

factory :post do
  association :author, :admin, factory: :user, name: 'John Doe'
  # or
  association :author, factory: [:user, :admin], name: 'John Doe'
end

# 「John Doe」という名前の管理ユーザーを作成します
create(:post).author

特性を他の特性内で使用して、それらの属性を混在させることができます。

factory :order do
  trait :completed do
    completed_at { 3.days.ago }
  end

  trait :refunded do
    completed
    refunded_at { 1.day.ago }
  end
end

最後に、特性は一時的な属性を受け入れることができます。

factory :invoice do
  trait :with_amount do
    transient do
      amount { 1 }
    end

    after(:create) do |invoice, evaluator|
      create :line_item, invoice: invoice, amount: evaluator.amount
    end
  end
end

create :invoice, :with_amount, amount: 2

コールバック(Callbacks)

factory_botは、いくつかのコードを挿入するための4つのコールバックを使用可能です。

  • after(:build)-ファクトリーのビルド後に呼び出されます( FactoryBot.build FactoryBot.createを介して)
  • before(:create)-ファクトリーが保存される前に呼び出されます( FactoryBot.createを介して)
  • after(:create)-ファクトリーが保存された後に呼び出されます( FactoryBot.createを介して)
  • after(:stub)-ファクトリーがスタブ化された後に呼び出されます( FactoryBot.build_stubbedを介して)

例:

# ビルド後に generate_hashed_pa​​ssword メソッドを呼び出すファクトリーを定義します。
factory :user do
  after(:build) { |user| generate_hashed_password(user) }
end

ブロック内にユーザーのインスタンスがあることに注意してください。
これは便利です。
同じファクトリーで複数のタイプのコールバックを定義することもできます。

factory :user do
  after(:build)  { |user| do_something_to(user) }
  after(:create) { |user| do_something_else_to(user) }
end

ファクトリーは、同じ種類のコールバックをいくつでも定義できます。
これらのコールバックは、指定された順序で実行されます。

factory :user do
  after(:create) { this_runs_first }
  after(:create) { then_this }
end

「create」を呼び出すと、「after_build」と「after_create」の両方のコールバックが呼び出されます。

また、標準属性と同様に、子ファクトリーは親ファクトリーからコールバックを継承します(定義することもできます)。

ブロックを実行するために複数のコールバックを割り当てることができます。
これは、同じコードを実行するさまざまな戦略を構築する場合に便利です(すべての戦略で共有されるコールバックがないため)。

factory :user do
  callback(:after_stub, :before_create) { do_something }
  after(:stub, :create) { do_something_else }
  before(:create, :custom) { do_a_third_thing }
end

すべてのファクトリーのコールバックをオーバーライドするには、FactoryBot.defineで定義します。

FactoryBot.define do
  after(:build) { |object| puts "Built #{object}" }
  after(:create) { |object| AuditLog.create(attrs: object.attributes) }

  factory :user do
    name { "John Doe" }
  end
end

Symbol#to_procに依存するコールバックを呼び出すこともできます:

# app/models/user.rb
class User < ActiveRecord::Base
  def confirm!
    # ユーザーアカウントを確認
  end
end

# spec/factories.rb
FactoryBot.define do
  factory :user do
    after :create, &:confirm!
  end
end

create(:user) # ユーザーを作成して確認する

ファクトリーの変更(Modifying factories)

一連のファクトリー(たとえば、gem開発者から)を与えられたが、それらをアプリケーションにより適合させるために変更したい場合は、次のことができます。
子ファクトリーを作成して属性を追加する代わりに、そのファクトリーを変更します。

gem がユーザーファクトリーを提供する場合:

FactoryBot.define do
  factory :user do
    full_name { "John Doe" }
    sequence(:username) { |n| "user#{n}" }
    password { "password" }
  end
end

追加の属性を追加した子ファクトリーを作成する代わりに:

FactoryBot.define do
  factory :application_user, parent: :user do
    full_name { "Jane Doe" }
    date_of_birth { 21.years.ago }
    gender { "Female" }
    health { 90 }
  end
end

代わりにそのファクトリーを変更できます。

FactoryBot.modify do
  factory :user do
    full_name { "Jane Doe" }
    date_of_birth { 21.years.ago }
    gender { "Female" }
    health { 90 }
  end
end

ファクトリーを変更するときは、コールバックを除き、必要な属性を変更できます。

ファクトリーに対して異なる動作をするため、FactoryBot.modify FactoryBot.defineブロックの外側で呼び出す必要があります。

警告:変更できるのは、ファクトリー(シーケンスまたはトレイトではない)とコールバックのみです(通常の場合と同じように*複合されます)。
だから、もし変更しているファクトリーは after(:create)コールバックを定義し、after(:create)を定義してもそれはオーバーライドされません。最初のコールバックの後に実行されます。

複数のレコードの作成または作成(Building or Creating Multiple Records)

場合によっては、ファクトリーの複数のインスタンスを一度に作成または構築する必要があります。

built_users   = build_list(:user, 25)
created_users = create_list(:user, 25)

これらのメソッドは、特定の量のファクトリーを構築または作成し、それらを配列として返します。
各ファクトリーの属性を設定するには、通常どおりハッシュを渡すことができます。

twenty_year_olds = build_list(:user, 25, date_of_birth: 20.years.ago)

build_stubbed_listは完全にスタブアウトされたインスタンスを提供します。

stubbed_users = build_stubbed_list(:user, 25) # スタブユーザーの配列

一度に2つのレコードを作成するための * _pairメソッドのセットもあります。

built_users   = build_pair(:user) # 2人のビルドされたユーザーの配列
created_users = create_pair(:user) # 作成された2人のユーザーの配列

複数の属性ハッシュが必要な場合、 attributes_for_listはそれらを生成します:

users_attrs = attributes_for_list(:user, 25) # 属性ハッシュの配列

リンティングファクトリー(Linting Factories)

factory_botは、既知のファクトリーのリントを許可します:

FactoryBot.lint

FactoryBot.lintは各ファクトリーを作成し、発生した例外をキャッチします
作成プロセス中。 FactoryBot :: InvalidFactoryError
可能性のある工場の工場(および対応する例外)のリスト作成されません。

FactoryBot.lintの推奨される使用法は、タスクで実行することです。
テストスイートが実行される前、before(:suite)で実行します。
※:あなたのテストの単一のテストを実行するときパフォーマンスに悪影響を及ぼします。

Rakeタスクの例:

# lib/tasks/factory_bot.rake
namespace :factory_bot do
  desc "すべてのFactoryBotファクトリーが有効であることを確認"
  task lint: :environment do
    if Rails.env.test?    
      conn = ActiveRecord::Base.connection
      conn.transaction do
        FactoryBot.lint
        raise ActiveRecord::Rollback
      end
    else
      system("bundle exec rake factory_bot:lint RAILS_ENV='test'")
      fail if $?.exitstatus.nonzero?
    end
  end
end

上記の例は、SQLトランザクションとロールバックを使用して、データベースをクリーンなままにします。

リントしたいファクトリーのみを渡すことで、ファクトリーを選択的にリントできます。

factories_to_lint = FactoryBot.factories.reject do |factory|
  factory.name =~ /^old_/
end

FactoryBot.lint factories_to_lint

これにより、「old_」という接頭辞が付いていないすべてのファクトリーがリントされます。

特性もリントできます。このオプションは、それぞれがファクトリーのすべての特性は、有効なオブジェクトを独自に生成します。
これは、traits: true lintメソッドに渡すことで有効になります:

FactoryBot.lint traits: true

これは、他の引数と組み合わせることもできます。

FactoryBot.lint factories_to_lint, traits: true

リンティングに使用される戦略を指定することもできます。

FactoryBot.lint strategy: :build

詳細なリンティングには、デバッグに役立つ各エラーの完全なバックトレースが含まれます。

FactoryBot.lint verbose: true

カスタム構造(Custom Construction)

factory_botを使用して、いくつかの属性を持つオブジェクトを構築する場合
initializeに渡されるか、単純な以外の何かをしたい場合、ビルドクラスで newを呼び出すと、デフォルトの動作をオーバーライドできます。
ファクトリーで「initialize_with」を定義します。

例:

class User
  attr_accessor :name, :email

  def initialize(name)
    @name = name
  end
end

# factories.rb
sequence(:email) { |n| "person#{n}@example.com" }

factory :user do
  name { "Jane Doe" }
  email

  initialize_with { new(name) }
end

build(:user).name # Jane Doe

factory_botはActiveRecordをそのまま使用できるように書かれていますが、Rubyクラスでも動作します。 ActiveRecordとの最大限の互換性のために、デフォルトのinitializeは、ビルドクラスで newを呼び出すことですべてのインスタンスをビルドします。
引数なし。
次に、属性ライターメソッドを呼び出して、すべての属性値。
ActiveRecordではうまくいきますが、実際にはうまくいきません
他のほぼすべてのRubyクラスで動作します。

次の目的で初期化子をオーバーライドできます。

  • initializeへの引数を必要とする非ActiveRecordオブジェクトを構築する
    *「new」以外のメソッドを使用してインスタンスをインスタンス化する
    *ビルド後にインスタンスを飾るなどのクレイジーなことをする

initialize_withを使用する場合、クラス自体を宣言する必要はありません。
「new」の呼び出し:ただし、呼び出したい他のクラスメソッドは。クラスで明示的に呼び出されます。

例えば:

factory :user do
  name { "John Doe" }

  initialize_with { User.build_with_name(name) }
end

また、attributesを呼び出すことにより「initialize_with」ブロック内のすべてのパブリック属性にアクセスできます

factory :user do
  transient do
    comments_count { 5 }
  end

  name "John Doe"

  initialize_with { new(attributes) }
end

これにより、すべての属性のハッシュが作成され、「new」に渡されます。
一時的な属性が含まれますが、ファクトリーで定義されている他のすべては適応されません。

FactoryBot.defineに含めることで、すべてのファクトリーに対して「initialize_with」を定義できます。

FactoryBot.define do
  initialize_with { new("Awesome first argument") }
end

「initialize_with」を使用する場合、「initialize_with」内からアクセスされる属性
ブロックはコンストラクターでのみ割り当てられます。

FactoryBot.define do
  factory :user do
    initialize_with { new(name) }

    name { 'value' }
  end
end

build(:user)
# runs
User.new('value')

オブジェクトを永続化するカスタムメソッド(Custom Methods to Persist Objects)

デフォルトでは、レコードを作成すると、インスタンスで「保存」が呼び出されます。それは常に理想的であるとは限らない可能性があります。

ファクトリーの to_create

factory :different_orm_model do
  to_create { |instance| instance.persist! }
end

作成時に永続化メソッドを完全に無効にするには、 skip_createを実行します

そのファクトリーの場合:

factory :user_without_database do
  skip_create
end

すべてのファクトリーで「to_create」をオーバーライドするには、

FactoryBot.defineブロック:

FactoryBot.define do
  to_create { |instance| instance.persist! }

  factory :user do
    name { "John Doe" }
  end
end

ActiveSupport Instrumentation

作成された工場(および構築戦略)を追跡するために、
ActiveSupport::Notifications」は、サブスクライブする方法を提供するために含まれています
1つの例は、に基づいてファクトリーを追跡することです
実行時間のしきい値。

ActiveSupport::Notifications.subscribe("factory_bot.run_factory") do |name, start, finish, id, payload|
  execution_time_in_seconds = finish - start

  if execution_time_in_seconds >= 0.5
    $stderr.puts "Slow factory: #{payload[:name]} using strategy #{payload[:strategy]}"
  end
end

別の例では、すべての工場とその使用方法を追跡します。

RSpecを使用している場合、それは追加するのと同じくらい簡単です。

before(:suite)および after(:suite)

factory_bot_results = {}
config.before(:suite) do
  ActiveSupport::Notifications.subscribe("factory_bot.run_factory") do |name, start, finish, id, payload|
    factory_name = payload[:name]
    strategy_name = payload[:strategy]
    factory_bot_results[factory_name] ||= {}
    factory_bot_results[factory_name][strategy_name] ||= 0
    factory_bot_results[factory_name][strategy_name] += 1
  end
end

config.after(:suite) do
  puts factory_bot_results
end

RailsプリローダーとRSpec

spring zeusなどのRailsプリローダーでRSpecを実行すると、
ファクトリーを作成するときに ActiveRecord::AssociationTypeMismatchエラーが発生します。

以下のように、関連付けを使用します。

FactoryBot.define do
  factory :united_states, class: Location do
    name { 'United States' }
    association :location_group, factory: :north_america
  end

  factory :north_america, class: LocationGroup do
    name { 'North America' }
  end
end

エラーは、テストスイートの実行中に発生します。

Failure/Error: united_states = create(:united_states)
ActiveRecord::AssociationTypeMismatch:
  LocationGroup(#70251250797320) expected, got LocationGroup(#70251200725840)

2つの可能な解決策は、プリローダーなしでスイートを実行するか、
または次のように、 FactoryBot.reloadをRSpec設定に追加します。

RSpec.configure do |config|
  config.before(:suite) { FactoryBot.reload }
end

参考:
factory_bot_rails
factory_bot

44
36
0

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
44
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?