factory_botはフィクスチャの代わりになるものです。 直感的な定義構文であり、複数の構築戦略(保存されたインスタンス、保存されないインスタンス、属性ハッシュ、スタブ化オブジェクト)、ファクトリ継承を含む同じクラスに対する複数のファクトリ(user、admin_user、など)に対応しています。

ドキュメントは以下に分かれています。

  • 初めて使う方は、手引きから始めると良いでしょう。
  • レシピには、よくある状況に対する創造的な解決策があります。
  • ウィキには他のソフトウェアと統合するときの検討事項が詳しく書かれています。
  • 便覧には、本プロジェクトのソフトウェアを頻繁に使う人向けの情報があります。

使用許諾

factory_bot is Copyright © 2008 Joe Ferris and thoughtbot. It is free software, and may be redistributed under the terms specified in the LICENSE file.

thoughtbotについて

factory_botはthoughtbot, inc.により保守され、資金提供されています。 thoughtbotの名前とロゴはthoughtbot, inc.の商標です。

私達はオープンソースソフトウェアが大好きです! 私達の他のプロジェクトを参照してください。 製品を設計、開発、成長させるための求人を募集しています

構築戦略

factory_botのファクトリを定義したら、組み込みの構築戦略や独自の構築戦略を使って構築できます。

こうした戦略は全て、ActiveSupport::Notificationsを使ってfactory_bot.run_factory計装に通知し、キー:name:strategy:traits:overrides:factoryを持つペイロードを渡します。

リストではないメソッド(.build.build_pair.createなど)は、必須の実引数であるファクトリ名を取ります。 また、省略できるトレイト名や、上塗りする属性のハッシュもあります。 最後にブロックを取れます。 このブロックは、生成されたオブジェクトを実引数とし、更新されたオブジェクトを返します。

リストのメソッド(.build_list.create_listなど)には2つの必須の実引数を持ちます。 ファクトリの名前と構築するインスタンスの数です。 また省略可能なトレイトと上塗りするものを取れます。 最後にブロックを取れます。 このブロックは生成されたオブジェクトとゼロ始まりの添字を実引数として取り、更新されたオブジェクトを返します。

build

FactoryBot.buildメソッドはinitialize_withにしたがってクラスのインスタンスを構築します。 既定では.newクラスメソッドを呼びます。 .build_listは複数のインスタンスを構築し、.build_pairは2つのインスタンスを構築する早道です。

initialize_withを呼んだ後、after_buildフックを呼びます。

関連はbuild構築戦略を使って構築されます。

create

FactoryBot.createメソッドはinitialize_withにしたがってクラスのインスタンスを構築し、to_createを使って永続化します。 .create_listクラスメソッドは複数のインスタンスを構築します。 また.create_pairは2つのインスタンスを構築する早道です。

initialize_withを呼んだ後、以下のフックを順番に呼びます。

  1. after_build
  2. before_create
  3. フックではないto_create
  4. after_create

関連はcreate構築戦略を使って構築されます。

to_createフックはオブジェクトの永続化方法を制御します。 オブジェクトとfactory_botの文脈を持つブロックを取り、副作用を見越して走ります。 既定では#save!を呼びます。

attributes_for

FactoryBot.attributes_forメソッドは、属性とその値を持つHashを、initialize_withを使って構築します。 attributes_for_pairメソッドとattributes_for_listメソッドは、build_pairbuild_listと似た動作です。

関連はnull構築戦略(構築されません)を使って構築されます。

フックは呼ばれません。

build_stubbed

FactoryBot.build_stubbedメソッドは、偽のActiveRecordオブジェクトを返します。 .build_stubbed_pairメソッドと.build_stubbed_listメソッドは、.build_pair.build_listと似た定義です。

initialize_withを使ってオブジェクトを構築します。 ただし、メソッドとデータを適切にスタブします。

  • idは(属性で上塗りされない限り)連番で設定されます。
  • created_atupdated_atは(属性で上塗りされない限り)現在時刻に設定されます。
  • 全てのActiveModel::Dirtyの変更の記録が消去されます。
  • persisted?は真です。
  • new_record?は偽です。
  • destroyed?は偽です。
  • 永続化メソッド(#connection#delete#save#updateなど)はRuntimeErrorを投げます。

オブジェクトを設定した後after_stubフックを呼びます。

null

FactoryBot.nullメソッドはnilを返します。 .null_pairメソッドはnilの対を与えます。 .null_listは欲しい数だけnilを与えます。 内部的に使われています。

FactoryBot.find_definitions

FactoryBot.find_definitionsメソッドはプロジェクトに亙る全てのfactory_botの定義を読み込みます。

読込順はFactoryBot.definition_file_paths属性で制御します。 既定の読込順は以下の通りです。

  1. factories.rb
  2. factories/**/*.rb
  3. test/factories.rb
  4. test/factories/**/*.rb
  5. spec/factories.rb
  6. spec/factories/**/*.rb

Rails

.find_definitionsメソッドは初期化後にfactory_bot_railsにより自動的に呼ばれます。 初期化時に(config/initializersなどで).definition_file_pathsを設定したり、Rails.application.config.factory_bot.definition_file_pathsで設定したりできます。

FactoryBot.define

factory_botで読み込まれる各ファイルはブロック付きのFactoryBot.defineで呼ばれることになります。 ブロックはFactoryBot::Syntax::Default::DSLのインスタンス内で評価され、factorysequencetraitなどのメソッドを利用できます。

ファクトリ

FactoryBot.defineブロック内ではファクトリを定義できます。 factoryを使って定義された全てのものは構築戦略を使って構築できます。

factoryメソッドは3つの実引数を取ります。 必須の名前、省略できるオプションのハッシュ、そして省略できるブロックです。

名前はSymbolにすることになっています。

オプション

  • :classは、構築するクラスです。 クラスないしStringやSymbol(#to_sに応答する任意のもの)にできます。 既定では親クラス名かファクトリ名の何れかです。
  • :parentはこのファクトリが継承する別のファクトリ名です。 既定ではnilです。
  • :aliasesはこのファクトリの別名です。 構築戦略で使えます。 既定では空リストです。
  • :traitsはこのファクトリを構築するときに既定で使われる既定のトレイトです。 既定で空リストです。

ブロック

ブロックを使ってファクトリを定義できます。 この中では以下のメソッドが使えます。

factory内でfactoryを使うと、暗示した親を持つ新しいファクトリを定義できます。

add_attribute

ファクトリの定義の中では、add_attributeメソッドで、オブジェクトが構築されるときに設定されるキーバリュー対を定義できます。

add_attributeメソッドは、名前(SymbolまたはString)とブロックの2つの実引数を取ります。 ブロックはこのオブジェクトが構築される度に呼ばれます。 ブロックは属性が構築戦略で上塗りされるときは呼ばれません。

代入ではRubyの属性セッターを呼びます。 例えば以下があるとします。

FactoryBot.define do
  factory :user do
    add_attribute(:name) { "Acid Burn" }
  end
end

このとき#name=セッターが使われます。

user = User.new
user.name = "Acid Burn"

早道についてはmethod_missingも参照してください。

関連

ファクトリブロック内でassociationを使うと、このオブジェクトに隣合うオブジェクトが追加で作られます。 この名前は、ActiveRecordの文脈だと一番わかりやすいでしょう。

associationメソッドは必須の名前と省略できるオプションを取ります。

オプションは0個以上のトレイト名 (Symbol) とそれに続く属性の上塗りのハッシュです。 この関連を構築するとき、factory_botはトレイトと与えられた属性の上塗りを使います。

早道についてはmethod_missingを参照してください。 各構築戦略の関連の扱いについての説明は構築戦略を参照してください。

系列

factory_botは2つの水準の系列に対応しています。 大域的なものとファクトリ固有のものです。

大域系列

Factory.defineブロックでは、sequenceメソッドを使うと、他のファクトリで共有できる大域系列が定義されます。

sequenceメソッドは、名前、省略できる実引数、ブロックを取ります。 名前はSymbolにすることになっています。

対応する実引数は、開始の値を表す数値(既定で1)と:aliases(既定で[])です。 開始の値は#nextに応答しなければなりません。

ブロックは値を実引数として取り、結果を返します。

系列の値は大域的に増加します。 複数の場所で:email_address系列を使うと、都度値が漸増します。

早道についてはmethod_missingを参照してください。

ファクトリ系列

系列はファクトリブロック内に留めておけます。 構文は大域系列と同じですが、漸増する値のスコープはファクトリ定義に限られます。

加えてfactoryブロックでsequenceを使うと、暗黙にその値にadd_attributeを呼びます。

以下の2つは似ていますが、2つ目の例は大域系列が存在しません。

# 大域系列
sequence(:user_factory_email) { |n| "person#{n}@example.com" }

factory :user do
  # 大域系列を使う
  email { generate(:user_factory_email) }
end
# ファクトリのスコープの系列
factory :user do
  sequence(:email) { |n| "person#{n}@example.com" }
end

トレイト

factory定義ブロック内でtraitメソッドを使うと、ファクトリの変更内容が名前付きで定義されます。

トレイトメソッドは名前 (Symbol) とブロックを取ります。 ブロックはfactory定義ブロックのように扱います。

早道についてはmethod_missingを参照してください。

method_missing

factory定義ブロックでは、add_attributeassociationsequencetraitを使ってファクトリを定義できます。 また、既定のmethod_missing定義を活かした早道も使えます。

未知のメソッド(例えばnameadminemailaccount)を呼ぶと、関連や系列やトレイトやファクトリの属性に繋がります。

  1. method_missingにブロックが渡されたとき、常に属性を定義します。 これにより属性に値を設定できます。

  2. method_missingに実引数としてキー:factoryを持つハッシュが渡されたとき、常に関連を定義します。 これにより関連に使うファクトリを上塗りできます。

  3. 同名の別のファクトリがあるとき、関連を定義します。

  4. 同名の大域系列があると、属性が定義されます。 系列から値が取り出されます。

  5. ファクトリに同名のトレイトがあるとき、このファクトリの全ての構築に対してトレイトを変えます。

method_missingを使ってみましょう。 以下の明示的な定義があるとします。

FactoryBot.define do
  sequence(:email) { |n| "person#{n}@example.com" }
  factory :account
  factory :organization

  factory :user, traits: [:admin] do
    add_attribute(:name) { "Lord Nikon" }
    add_attribute(:email) { generate(:email) }
    association :account
    association :org, factory: :organization

    trait :admin do
      add_attribute(:admin) { true }
    end
  end
end

上記はもっと暗黙な定義にできます。

FactoryBot.define do
  sequence(:email) { |n| "person#{n}@example.com" }
  factory :account
  factory :organization

  factory :user do
    name { "Lord Nikon" }      # `add_attribute`なし
    admin                      # :traitsなし
    email                      # `add_attribute`なし
    account                    # `association`なし
    org factory: :organization # `association`なし

    trait :admin do
      admin { true }
    end
  end
end

traits_for_enum

factory定義ブロックには、traits_for_enumメソッドという補助機能があります。 任意のオブジェクトに、いくつかの値のうちのどれかになる属性を持たせます。 元となった着想はActiveRecord::Enumですが、限られた値の集合がある任意の属性に適用できます。

このメソッドにより、それぞれの値にトレイトが作られます。

traits_for_enumメソッドは、必須の属性名と省略できる値の集合を取ります。 値は任意のEnumerableにできます。 例えばArrayやHashです。 既定では値はnilです。

値がArrayのとき、このメソッドは配列内の各要素にトレイトを定義します。 トレイト名は配列の要素であり、同じ配列の要素に属性を設定します。

値がHashのとき、このメソッドはキーに基づくトレイトを定義し、属性を値に設定します。 トレイト名はキーであり、属性を値に設定します。

値が何らかのEnumerableのとき、ArrayまたはHashのように扱います。 このとき、#eachがHashのように対で反復するかどうかに基づきます。

値がnilのとき、複数形になった属性名に因むクラスメソッドを使います。

FactoryBot.define do
  factory :article do
    traits_for_enum :visibility, [:public, :private]
    # trait :public do
    #   visibility { :public }
    # end
    # trait :private do
    #   visibility { :private }
    # end

    traits_for_enum :collaborative, draft: 0, shared: 1
    # trait :draft do
    #   collaborative { 0 }
    # end
    # trait :shared do
    #   collaborative { 1 }
    # end

    traits_for_enum :status
    # Article.statuses.each do |key, value|
    #   trait key do
    #     status { value }
    #   end
    # end
  end
end

skip_createとto_create、そしてinitialize_with

skip_createメソッドとto_createメソッドとinitialize_withメソッドは、factory_botによる構築戦略とのやり取りの仕方を制御します。

これらのメソッドはfactory定義ブロック内で呼べます。 作用はそのファクトリに留まります。 FactoryBot.define内にすると大域的な変更として作用します。

initialize_with

initialize_withメソッドはブロックを取り、ファクトリのクラスのインスタンスを返します。 attributesメソッドを使うことができます。 このメソッドはオブジェクトの全てのフィールドと値からなるハッシュです。

既定の定義は以下です。

initialize_with { new }

to_create

to_createメソッドはFactoryBot.create戦略を制御します。 このメソッドはinitialize_withで構築されたオブジェクトとfactory_botの文脈を取るブロックを取ります。 文脈にはtransientブロックからの追加のデータがあります。

既定の定義は以下です。

to_create { |obj, context| obj.save! }

skip_createメソッドはto_createを何もしないように変える早道です。 これによりcreate戦略をbuildの同意語として使えます。 ただしcreateフックは追加で掛かっています。

transient

factory定義ブロック内では、クラスのインスタンスを構築することが目標です。 factory_botはこれをするものですが、文脈でのデータの記録をつけています。 データをこの文脈に設定するにはtransientブロックを使います。

transientブロックはfactory定義ブロックのように扱います。 しかし設定した属性、関連、トレイト、系列は最終的なオブジェクトに影響しません。

これはフックto_createとの取り合わせで一番有用です。

フック

factory定義ブロックやFactoryBot.defineブロック内部では、afterメソッドやbeforeメソッドやcallbackメソッドが使えます。 これにより構築戦略の一部にフックを掛けられます。

factory定義ブロック内では、これらのコールバックはそのファクトリのみのスコープとなります。 FactoryBot.defineブロック内では、全てのファクトリに対して大域的になります。

callback

callbackメソッドでは、任意のfactory_botのコールバックに名前でフックを掛けられます。 構築戦略の便覧に見られる通り、予め定義された名前はbefore_allafter_buildbefore_createafter_createafter_stubafter_allです。

このメソッドはスプラットされる名前とブロックを取ります。 名前のどれかが活性になる度にブロックが呼ばれます。 ブロックは#to_procに応答する任意のものにできます。

このブロックは2つの実引数を取ります。 1つはファクトリのインスタンスで、もう1つはfactory_botの文脈です。 文脈はtransient属性を持ちます。

同じコールバック名を複数回フックに掛けられます。 ブロックは全て、定義された順に実行されます。 コールバックは親から継承します。 親のコールバックがまず実行されます。

afterメソッドとbeforeメソッド

afterメソッドとbeforeメソッドはcallbackにいい感じの構文を加えています。

after(:create) do |user, context|
  user.post_first_article(context.article)
end

callback(:after_create) do |user, context|
  user.post_first_article(context.article)
end

FactoryBot.modify

FactoryBot.modifyクラスメソッドは使えるfactoryメソッドを上塗りしてブロックを定義します。 ブロック内で呼べる唯一のメソッドです。

このブロック内のfactoryメソッドは必須のファクトリ名とブロックを取ります。 他の全ての実引数は無視されます。 ファクトリ名は定義済みでなければなりません。 ブロックは通常のファクトリ定義ブロックです。 なおフックは消去できず、合わさったままになります。

これを使いたい状況になる理由についての詳細は、手引きを参照してください。

FactoryBot.lint

FactoryBot.lintメソッドは各ファクトリを試して失敗したときにFactoryBot::InvalidFactoryErrorを投げます。

以下の省略できる実引数を取れます。

  • スプラットされるファクトリ名です。 これは1つのみにリントの対象を制限します。 既定は全てです。
  • :strategyは使う構築戦略です。 既定は:createです。
  • :traitsは各トレイトの構築も試みるかどうかを表します。 既定はfalseです。
  • :verboseはエラーでスタックトレースを表示するかどうかです。 既定はfalseです。

.lintをシステムにフックを掛けるお勧めの工夫については、手引きで説明されています。

FactoryBot.register_strategy

FactoryBot.register_strategyメソッドは構築戦略の加え方です。

2つの必須の実引数である名前とクラスを取ります。 名前はSymbolで、FactoryBot::Syntax::Methods下に現すメソッドを登録します。

クラスはメソッドassociationresultを定義しなければなりません。

associationメソッドはFactoryRunnerのインスタンスを取ります。 このランナーを#runで走らせられます。 このとき戦略名(既定は現在のもの)と省略できるブロックを渡します。 ブロックは関連が構築された後に呼ばれ、構築されたオブジェクトが渡されます。

resultメソッドは(initialize_withを使って)このファクトリで構築されたオブジェクトを取り、この構築戦略用のファクトリの結果を返します。

準備

インストール手順は様々です。 もしあれば使っているフレームワークに依りますし、テストフレームワークについても考えられます。

インストール手順が私達の手の届かないコードに基づいて様々であるため、そうしたドキュメントはウィキで更新され続けます。 フレームワークが変わったときにウィキを編集することをお勧めします。

以降では最もよくある準備をドキュメントとして書きます。 しかし詳細はウィキをあたってください。

Gemfileの更新

Railsを使っている場合は次のようにしてください。

gem "factory_bot_rails"

Railsを使っていない場合は次のようにしてください。

gem "factory_bot"

詳細については私達のウィキを参照してください。

テストスートの構成

RSpec

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

Test::Unit

class Test::Unit::TestCase
  include FactoryBot::Syntax::Methods
end

詳細については私達のウィキを参照してください。

Bundler無しで使う

Bundlerを使わないとき、gemがインストールされて呼ばれていることを確認してください。

require 'factory_bot'

一旦requireされたら、spec/factoriesまたはtest/factoriesのディレクトリ構造があるとして、以下を走らせるだけで済みます。

FactoryBot.find_definitions

ファクトリ用の個別のディレクトリ構造を使っているとき、定義を見付けようとする前に定義ファイルパスを代えられます。

FactoryBot.definition_file_paths = %w(custom_factories_directory)
FactoryBot.find_definitions

ファクトリの個別のディレクトリがなく、行内に定義したいときも可能です。

require 'factory_bot'

FactoryBot.define do
  factory :user do
    name { 'John Doe' }
    date_of_birth { 21.years.ago }
  end
end

Railsの事前読込器とRSpec

RSpecをspringzeusといったRailsの事前読込器付きで走らせるとき、関連付きのファクトリを作るときに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つの解決策が考えられます。 1つはテストスートを事前読込器無しで走らせることです。 もう1つはRSpecの構成にFactoryBot.reloadを追加することです。 以下のような感じです。

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

ファクトリの定義

ファクトリ名と属性

各ファクトリには名前と属性の一式があります。 名前は既定でオブジェクトのクラスを推測するために使われます。

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

クラスを明示的に指定

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

# 以下はUserクラスを使います(こうしなければAdminが推測されていました)
factory :admin, class: "User"

定数が使えるときは定数も渡せます(なおこれは大きいRailsアプリケーションではテストの効率性の問題を引き起こすことがあります。 定数の参照が積極読込を引き起こすためです)。

factory :access_token, class: User

定義ファイルパス

ファクトリはどこでも定義できます。 しかしファクトリが以下の場所のファイルで定義された場合、FactoryBot.find_definitionsを読んだ後に自動的に読み込まれます。

factories.rb
factories/**/*.rb
test/factories.rb
test/factories/**/*.rb
spec/factories.rb
spec/factories/**/*.rb

ハッシュ属性

Rubyのブロック構文があるため、(例えば直列化された列やJSONの列用に)属性をHashとして定義するには、2対の波括弧が必要です。

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

代えてお好みでdoendの構文にすることもできます。

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

しかし値をハッシュとして定義するとオブジェクトを構築するときにハッシュ内に値を設定するのが複雑になります。 その代わりにfactory_bot自体を使うようにしてください。

factory :program do
  configuration { attributes_for(:configuration) }
end

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

この方法ではより簡単に構築の際に値を設定できます。

create(
  :program,
  configuration: attributes_for(
    :configuration,
    auto_resolve: true,
  )
)

ベストプラクティス

お勧めは、各クラスに1つのファクトリがあるようにし、そのクラスのインスタンスを作るのに必要な、最も単純な属性一式を提供することです。 ActiveRecordオブジェクトを作る場合、これが意味するのは検証を通じて求められる属性のみを与え、既定値を持たせないということです。 他のファクトリは各クラスについて共通する筋書を押さえる上で継承を通じて作れます。

同じ名前で複数のファクトリを定義しようとするとエラーが投げられます。

静的属性

(ブロックの無い)静的属性はfactory_bot 5では使えなくなりました。 削除する決断についての詳細はこちらのブログの記事をお読みいただけます。

ファクトリを使う

構築戦略

factory_botは複数の異なる構築戦略に対応しています。 buildcreateattributes_forbuild_stubbedです。

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

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

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

# Ruby 3.0のパターン合致代入の対応と統合します。
attributes_for(:user) => {email:, name:, **attrs}

# 全ての定義された属性をスタブ化したオブジェクトを返します。
stub = build_stubbed(:user)

# 上記のどのメソッドにも、ブロックを渡せば返却されたオブジェクトがもたらされます。
create(:user) do |user|
  user.posts.create(attributes_for(:post))
end

build_stubbedとMarshal.dump

なおbuild_stubbedで作られたオブジェクトはMarshal.dumpで直列化できません。 factory_botはこれらのオブジェクトに特異メソッドを定義するからです。

複数のレコードの構築と作成

ファクトリの複数のインスタンスを一括で作成したり構築したりしたいときがあります。

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)

各ファクトリに異なる属性を設定するためには、これらのメソッドにブロックを渡せます。 そこでファクトリと仮引数として添字を持たせます。

twenty_somethings = build_list(:user, 10) do |user, i|
  user.date_of_birth = (20 + i).years.ago
end

create_listは保存されたインスタンスをブロックに保存します。 インスタンスを変更したらまた保存しなければなりません。

twenty_somethings = create_list(:user, 10) do |user, i|
  user.date_of_birth = (20 + i).years.ago
  user.save!
end

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) # 属性ハッシュの配列

属性の上塗り

使われた戦略に依らず、ハッシュを渡して定義された属性を上塗りできます。

# Userインスタンスを構築しfirst_name特性を上塗りします
user = build(:user, first_name: "Joe")
user.first_name
# => "Joe"

Ruby 3.1のHash表記でのバリューの省略対応は属性の上塗りにぴったり馴染む機能で、変数名の繰り返しになるところを削減できます。

first_name = "Joe"

# Userインスタンスを構築しfirst_name特性を上塗りします
user = build(:user, first_name:)
user.first_name
# => "Joe"

別称

factory_botでは既存のファクトリに別称を定義して簡単に再利用できます。 この機能がしっくりくるのは、例えば、Postオブジェクトに著者の属性があり、実際にはUserクラスのインスタンスを参照しているときです。 通常factory_botは関連名からファクトリ名を推定できるものの、この場合は著者のファクトリを探して徒労になります。 そのため利用者のファクトリに別称を付ければ別称名で使えます。

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

factory :post do
  # 別称を使うと以下の代わりに著者で書けます。
  # association :author, factory: :user
  author
  title { "How to read a book effectively" }
  body { "There are five steps involved." }
end

factory :comment do
  # 別称を使うと以下の代わりに評論家で書けます。
  # association :commenter, factory: :user
  commenter
  body { "Great article!" }
end

依存属性

属性は文脈を使って他の属性の値に基づくものにできます。 属性は動的な属性ブロックに処理を譲ります。

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ブロック内に定義します。

factory :user do
  name { "Zero Cool" }
  birth_date { age&.years.ago }

  transient do
    age { 11 } # 上の`birth_date`に設定するためだけに使われます。
  end
end

他の属性付き

他の属性から一過的属性を使えます(依存属性を参照)。

factory :user do
  transient do
    rockstar { true }
  end

  name { "John Doe#{" - Rockstar" if rockstar}" }
end

create(:user).name
#=> "John Doe - ROCKSTAR"

create(:user, rockstar: false).name
#=> "John Doe"

attributes_for付き

一過的属性はattributes_for内では無視され、属性が存在して上塗りしようとしたとしても、モデルに設定されません。

コールバック付き

factory_botのコールバックで評価された定義自体を使う必要があるとき、(定義に)2つ目のブロック引数を宣言してそこから一過的属性を使う必要があります。 こうすると、最終的な評価された値が表されます。

factory :user do
  transient do
    upcased { false }
  end

  name { "John Doe" }

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

create(:user).name
#=> "John Doe"

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

関連付き

一過的関連はfactory_botでは対応していません。 一過的ブロック内の関連は通常の一過的でない関連として扱われます。

必要であれば、一般には一過的属性内でファクトリを構築して回避できます。

factory :post

factory :user do
  transient do
    post { build(:post) }
  end
end

メソッド名と予約語の属性

属性が既存のメソッドや予約語(DefinitionProxy内の全メソッド)と競合するとき、add_attributeで定義できます。

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

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

継承

入れ子のファクトリ

入れ子のファクトリでは、共通する属性を繰り返すことなく、同じクラスに対して複数のファクトリを作れます。

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

ベストプラクティス

前述した通り、作成に必須の属性のみを持つ各クラスの基礎的なファクトリを定義するのが良いでしょう。 それからこの基礎的な親から継承するより具体的なファクトリを作るのです。

関連

暗黙定義

ファクトリ内で関連を設定することができます。 ファクトリ名が関連名と同じとき、ファクトリ名を省けます。

factory :post do
  # ...
  author
end

明示定義

関連を明示的に定義できます。 属性を上塗りするときに特にしっくりくることがあります。

factory :post do
  # ...
  association :author
end

行内定義

通常の属性内に行内で関連を定義することもできます。 ただしattributes_for戦略を使うときは値がnilになることに注意してください。

factory :post do
  # ...
  author { association :author }
end

ファクトリの指定

異なるファクトリを指定することもできます(別称も役立つかもしれませんが)。

暗黙には以下とします。

factory :post do
  # ...
  author factory: :user
end

明示的には以下とします。

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

行内では以下とします。

factory :post do
  # ...
  author { association :user }
end

属性の上塗り

関連で属性の上塗りもできます。

暗黙には以下とします。

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

明示的には以下とします。

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

もしくはファクトリの属性を使って行内でもできます。

factory :post do
  # ...
  author_last_name { "Writely" }
  author { association :author, last_name: author_last_name }
end

関連の上塗り

属性の上塗りは紐付くオブジェクトを結び付けるのに使えます。

FactoryBot.define do
  factory :author do
    name { 'Taylor' }
  end

  factory :post do
    author
  end
end

eunji = build(:author, name: 'Eunji')
post = build(:post, author: eunji)

Ruby 3.1のHash表記でのバリューの省略対応は属性の上塗りにぴったり馴染む機能で、変数名の繰り返しになるところを削減できます。

author = build(:author, name: 'Eunji')

post = build(:post, author:)

構築戦略

関連は既定で親オブジェクトと同じ構築戦略を使います。

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

# UserとPostを構築して保存します。
post = create(:post)
post.new_record?        # => false
post.author.new_record? # => false

# Userを構築して保存します。またPostを構築するものの保存はしません。
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

# Userを構築し、Postを構築します。ただし何れも保存しません。
post = build(:post)
post.new_record?        # => true
post.author.new_record? # => true

なおstrategy: :buildオプションはassociationの明示的な呼び出しで渡さなければなりません。 暗黙の関連では使えません。

factory :post do
  # ...
  author strategy: :build    # <<< これはうまくいき「ません」。author_idがnilになります。

系列

大域系列

特定の形式(例えばEメールアドレス)の一意な値は系列を使って生成できます。 系列は定義ブロックでsequenceを読んで定義されます。 系列の値はgenerateを読んで生成されます。

# 新しい系列を定義します。
FactoryBot.define do
  sequence :email do |n|
    "person#{n}@example.com"
  end
end

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

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

動的属性付き

系列は動的属性で使えます。

FactoryBot.define do
  sequence :email do |n|
    "person#{n}@example.com"
  end
end

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

暗黙属性として

もしくは暗黙属性として以下とします。

FactoryBot.define do
  sequence :email do |n|
    "person#{n}@example.com"
  end
end

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

なお系列を暗黙属性として定義すると、ファクトリに系列として同じ名前があるときにうまくいかなくなります。

ファクトリ系列

また、特定のファクトリでのみ使われる系列を定義することもできます。

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

Ruby 2.7の連番仮引数により、行内定義はもっと更に短く書けます。

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

初期値

初期値を上塗りできます。 #nextメソッドに応答する任意の値で動作します(例えば1、2、3、「a」、「b」、「c」)。

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

ブロック無し

ブロック無しでは値はそれ自身を漸増させ、初期値から始めていきます。

factory :post do
  sequence(:position)
end

なお系列用の値は、#nextに応答する限り、任意のEnumerableインスタンスにできます。

factory :task do
  sequence :priority, %i[low medium high urgent].cycle
end

別称

系列には別称も付けられます。 系列の別称は同じ計数を共有します。

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

# :emailの計数を漸増させます。:senderと:receiverと共有しています。
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」、などとなります。

系列URI

特定の系列を操作したい理由は沢山あります。

  • 単一の値を生成:generate
  • 複数の値を生成:generate_list
  • FactoryBot.rewind_sequencesで新しい値に設定
  • 巻き戻し:rewind_sequence

これを達成するには、所望の系列を参照できる必要があります。 これはその系列の一意なURIで実現できます。

URI合成

各URIは3つの名前が組み合わさったものです。

位置名前必要かどうか
1.ファクトリ名条件付き - 系列はファクトリないしファクトリトレイト内で定義されます
2.トレイト名条件付き - 系列はトレイト内で定義されます
3.系列名必ず必要

URIは個々のシンボルとして入力されます。

generate(:my_factory_name, :my_trait_name, :my_sequence_name)

あるいは 個々の文字列として入力されます。

generate('my_factory_name', 'my_trait_name', 'my_sequence_name')

あるいは 単一のリソース文字列として入力されます。

generate("my_factory_name/my_trait_name/my_sequence_name")

完全なURIの例

以下の例では詳しく全てのあり得るシナリオを説明しています。 コメントでは、それぞれの系列の値を生成するのに使われるURIを示しています。

FactoryBot.define do
  sequence(:sequence) {|n| "global_sequence_#{n}"}
  # generate(:sequence)

  trait :global_trait do
    sequence(:sequence) {|n| "global_trait_sequence_#{n}"}
    # generate(:global_trait, :sequence)
  end

  factory :user do
    sequence(:sequence) {|n| "user_sequence_#{n}"}
    # generate(:user, :sequence)

    trait :user_trait do
      sequence(:sequence) {|n| "user_trait_sequence_#{n}"}
      # generate(:user, :user_trait, :sequence)
    end

    factory :author do
      sequence(:sequence) {|n| "author_sequence_#{n}"}
      # generate(:author, :sequence)

      trait :author_trait do
        sequence(:sequence) {|n| "author_trait_sequence_#{n}"}
        # generate(:author, :author_trait, :sequence)
      end
    end
  end
end

複数URI

1つの系列に複数のURIを持たせることができます。

ファクトリやトレイトに別称があるとき、系列はそれぞれの別称、また別称の組み合わせ毎に、追加でURIを持ちます。

この例では、同じ系列を4つの異なる方法で参照できます。

factory :user, aliases: [:author] do
  trait :user_trait, aliases: [:author_trait] do
    sequence(:sequence) {|n| "author_trait_sequence_#{n}"}
  end
end

# generate(:user,   :user_trait,   :sequence)
# generate(:user,   :author_trait, :sequence)
# generate(:author, :user_trait,   :sequence)
# generate(:author, :author_trait, :sequence)

重要

  • どれだけ深く入れ子になっていても、URIのファクトリ名の構成要素は常に、その系列が定義されているファクトリです。 決して親ファクトリではありません。

  • ファクトリが系列を継承するとき、URIはそれが定義されたファクトリを参照しなくてはなりません。 使われているファクトリではありません。

巻き戻し

系列は初めの値に巻き戻すこともできます。

全ての系列

FactoryBot.rewind_sequencesとすると、大域でファクトリである全ての系列が巻き戻ります。

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

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

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

generate(:user, :email) # "user1@example.com"
generate(:user, :email) # "user2@example.com"
generate(:user, :email) # "user3@example.com"

FactoryBot.rewind_sequences

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

個々の系列

FactoryBot.rewind_sequences系列URIを渡すと、個々の系列を巻き戻せます。

FactoryBot.define do
  sequence(:email) {|n| "global_email_#{n}@example.com" }

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

FactoryBot.rewind_sequence(:email)
generate(:email)
#=> "global_email_1@example.com"

factoryBot.rewind_sequence(:user, :email)
generate(:user, :email)
#=> "user_email_1@example.com"

値の設定

コンソールで試行錯誤するとき、系列を特定の値に設定できると、大変便利です。 これはFactoryBot.set_sequence系列URIと新しい値を渡すと実現できます。

大域系列

大域系列は系列名と新しい値で設定されます。

FactoryBot.define do
  sequence(:char, 'a') {|c| "global_character_#{c}" }

  factory :user do
    sequence(:name, %w[Jane Joe Josh Jayde John].to_enum)

    trait :with_email do
      sequence(:email) {|n| "user_#{n}@example.com" }
    end
  end
end

##
# 文字
generate(:char) # "global_character_a"
FactoryBot.set_sequence(:char, 'z')
generate(:char) # "global_character_z"

##
# 利用者名
generate(:user, :name) # "Jane"
FactoryBot.set_sequence(:user, :name, 'Jayde')
generate(:user, :name) # "Jayde"

##
# 利用者のEメール
generate(:user, :with_email, :email) # "user_1@example.com"
FactoryBot.set_sequence(:user, :with_email, :email, 1_234_567)
generate(:user, :with_email, :email) # "user_1234567@example.com"

補足

  • 新しい値は系列の要素の集合に合致しなければなりません。 Integerベースの系列にStringを渡すことはできません!

  • 整数ベースの系列、例えばレコードIDは、値として任意の正の整数を受け付けます。

  • 取り得る要素が決まっている集まりの系列は、その中にある任意の値を取れます。

  • 無制限の系列、例えば文字のsequence(:unlimited,'a')は、既定の最大の探索時間である3秒内に見つからなければ、時間切れになります。

  • 時間制限を構成できます:FactoryBot.sequence_setting_timeout = 1.5

系列の生成

直接系列を生成できるようにし、ただしオブジェクトを構築せずにおくと、テストが本当に高速になることがあります。 これは単一の値の:generate系列URIを渡したり、系列の値の配列に:generate_listを渡したりして実現できます。

FactoryBot.define do
  sequence(:char, 'a') {|c| "global_character_#{c}" }

  factory :user do
    sequence(:name, %w[Jane Joe Josh Jayde John].to_enum)

    trait :with_age do
      sequence(:age, 21)
    end
  end
end

##
# 文字
generate(:char) # "global_character_a"
generate_list(:char, 2) # ["global_character_b", "global_character_c"]
generate(:char) # "global_character_d"

##
# 利用者名
generate(:user, :name) # "Jane"
generate_list(:user, :name, 3) # ['Joe', 'Josh', 'Jayde']
generate(:user, :name) # "John"

##
# 利用者の年齢
generate(:user, :with_age, :age) # 21
generate_list(:user, :with_age, :age, 5) # [22, 23, 24, 25, 26]
generate(:user, :with_age, :age) # 27

スコープ

系列のブロックからスコープ内の属性を参照することもあるでしょう。 この場合、スコープを与えなければなりません。 そうしなければ例外が投げられます。

FactoryBot.define do
  factory :user do
    sequence(:email) { |n| "#{name}-#{n}@example.com" }
  end
end

generate(:user, :email)
# ArgumentError, Sequence user:email failed to return a value. Perhaps it needs a scope to operate? (scope: <object>)

jester = build(:user, name: "Jester")
jester.email # "Jester-1@example.com"

generate(:user, :email, scope: jester)
# "Jester-2@example.com"

generate_list(:user, :email, 2, scope: jester)
# ["Jester-3@example.com", "Jester-4@example.com"]

テストのとき、スコープは参照されている属性に応答する任意のオブジェクトにできます。

require 'ostruct'

FactoryBot.define
  factory :user do
    sequence(:info) { |n| "#{name}-#{n}-#{age + n}" }
  end
end

test_scope = OpenStruct.new(name: "Jester", age: 23)

generate_list('user/info', 3, scope: test_scope)
# ["Jester-1-24", "Jester-2-25", "Jester-3-26"]

一意性

一意性制約に取り組むときは、生成される系列値と競合する値を渡して上塗りしないようにご注意ください。

以下の例ではEメールが両方の利用者で同じになります。 Eメールが一意でなければならないとき、このコードはエラーになります。

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

FactoryBot.create(:user, email: "person1@example.com")
FactoryBot.create(:user)

トレイト

トレイトでは属性をグループに纏めて任意のファクトリに適用できます。

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

なおトレイトを暗黙属性として定義すると、トレイトと同じ名前のファクトリや系列があるときに、うまくいかなくなります。

トレイトを使う

factory_botでインスタンスを構築するとき、トレイトにはSymbolのリストも渡せます。

factory :user do
  name { "Friendly User" }

  trait :active do
    name { "John Doe" }
    status { :active }
  end

  trait :admin do
    admin { true }
  end
end

# :active状態で名前が「Jon Snow」の管理者の利用者を作ります。
create(:user, :admin, :active, name: "Jon Snow")

この機能はbuildbuild_stubbedattributes_forcreateで動きます。

create_listメソッドとbuild_listメソッドも対応しています。 2つ目の仮引数に作成・構築するインスタンス数を渡すことをご留意ください。 このファイルの「複数レコードの構築と作成」でドキュメントに書かれている通りです。

factory :user do
  name { "Friendly User" }

  trait :active do
    name { "John Doe" }
    status { :active }
  end

  trait :admin do
    admin { true }
  end
end

# 3人の管理利用者を作ります。:active状態があり、名前は「Jon Snow」です
create_list(:user, 3, :admin, :active, name: "Jon Snow")

列挙トレイト

列挙属性を持つActive Recordがあるとします。

class Task < ActiveRecord::Base
  enum status: {queued: 0, started: 1, finished: 2}
end

factory_botは列挙体の取り得る値それぞれについて自動的にトレイトを定義します。

FactoryBot.define do
  factory :task
end

FactoryBot.build(:task, :queued)
FactoryBot.build(:task, :started)
FactoryBot.build(:task, :finished)

手でトレイトを書くのは億劫であり、必要ありません。

FactoryBot.define do
  factory :task do
    trait :queued do
      status { :queued }
    end

    trait :started do
      status { :started }
    end

    trait :finished do
      status { :finished }
    end
  end
end

全てのファクトリで列挙属性にトレイトを自動的に定義することが望ましくなければ、この機能をFactoryBot.automatically_define_enum_traits = falseで無効にできます。

その場合でも特定のファクトリで列挙属性にトレイトを明示的に定義できます。

FactoryBot.automatically_define_enum_traits = false

FactoryBot.define do
  factory :task do
    traits_for_enum(:status)
  end
end

他の列挙できる値にこの機能を使うこともできます。 特別にActive Recordの列挙属性に結び付いているわけではありません。

配列の場合は以下です。

class Task
  attr_accessor :status
end

FactoryBot.define do
  factory :task do
    traits_for_enum(:status, ["queued", "started", "finished"])
  end
end

あるいはハッシュの場合は以下です。

class Task
  attr_accessor :status
end

FactoryBot.define do
  factory :task do
    traits_for_enum(:status, { queued: 0, started: 1, finished: 2 })
  end
end

属性の優先度

同じ属性を定義するトレイトはAttributeDefinitionErrorsを投げません。 最後に属性を定義したトレイトが優先されます。

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

  trait :active do
    name { "John Doe" }
    status { :active }
    login { "#{name} (active)" }
  end

  trait :inactive do
    name { "Jane Doe" }
    status { :inactive }
    login { "#{name} (inactive)" }
  end

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

  factory :active_admin,   traits: [:active, :admin]   # loginは「admin-John Doe」になります。
  factory :inactive_admin, traits: [:admin, :inactive] # loginは「Jane Doe (inactive)」になります。
end

子のファクトリの内部

子のファクトリの内部でトレイトから与えられる個々の属性を上塗りできます。

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

  trait :active do
    name { "John Doe" }
    status { :active }
    login { "#{name} (M)" }
  end

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

ミックスインとして

トレイトはファクトリの外側で定義し、共有する属性を組み合わせるためにミックスインとして使えます。

FactoryBot.define do
  trait :timestamps do
    created_at { 8.days.ago }
    updated_at { 4.days.ago }
  end
  
  factory :user, traits: [:timestamps] do
    username { "john_doe" }
  end
  
  factory :post do
    timestamps
    title { "Traits rock" }
  end
end

関連付き

トレイトは関連とも簡単に使えます。

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'
  # もしくは以下です。
  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, context|
      create :line_item, invoice: invoice, amount: context.amount
    end
  end
end

create :invoice, :with_amount, amount: 2

コールバック

factory_botでは、6種類のコールバックを作れます。

コールバック時機
before(:all)独自の戦略を含めて、どの戦略がオブジェクトの構築に使われたときも、その前に呼ばれます
before(:build)ファクトリが(FactoryBot.buildFactoryBot.createで)オブジェクトを構築する前に呼ばれます
after(:build)ファクトリが(FactoryBot.buildFactoryBot.createで)オブジェクトを構築した後に呼ばれます
before(:create)ファクトリが(FactoryBot.createで)オブジェクトを保存する前に呼ばれます
after(:create)ファクトリが(FactoryBot.createで)オブジェクトを保存した後に呼ばれます
after(:stub)ファクトリが(FactoryBot.build_stubbedで)オブジェクトをスタブした後に呼ばれます
after(:all)独自の戦略を含めて、どの戦略が完了したときも、その後に呼ばれます

構築後にオブジェクト自体のメソッドを呼ぶ

##
# 利用者ファクトリが構築された後に generate_hashed_password
# メソッドを呼ぶファクトリを定義します。
#
# なお、ブロックにはオブジェクトのインスタンスがあります。
#
factory :user do
  after(:build) { |user, context| generate_hashed_password(user) }
end

オブジェクト自体の :after_create コールバックを飛ばす

##
# モデル自体の :after_create コールバックを無効にします。
# このコールバックは作成時にEメールを送るものです。
# それから作成後に再び有効にします。
#
factory :user do
  before(:all){ User.skip_callback(:create, :after, :send_welcome_email) }
  after(:all){ User.set_callback(:create, :after, :send_welcome_email) }
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

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) # 利用者を作成して確かめます。

コールバックの順序

ファクトリの変更

ファクトリ一式が与えられているものの(gemの開発者からとしましょう)、アプリケーションにもっと合うように代えたいとき、子ファクトリを作ってそこに属性を加える代わりに、ファクトリを変更できます。

gemが以下のようにUserファクトリを与えているとします。

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 }
    health { 90 }
  end
end

その代わりとしてファクトリを変更できます。

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

ファクトリを変更するとき、どんな属性も(コールバックを除いて)お好みで代えられます。

FactoryBot.modifyFactoryBot.defineの外側で呼ばなければなりません。 ファクトリを違った風に操作するからです。

注意点:ファクトリのみ変更でき(系列やトレイトはできません)、コールバックは通常のものと同様に付属したままです。 そのため、変更しているファクトリでafter(:create)コールバックを定義するとき、after(:create)を定義しても上塗りされません。 最初のコールバックの後に実行されます。

ファクトリのリント

factory_botは既知のファクトリをリントできます。

FactoryBot.lint

FactoryBot.lintは各ファクトリを作って作成の仮定で投げられた例外を捕えます。 作成されなかったファクトリのリスト(と対応する例外)を持つFactoryBot::InvalidFactoryErrorが投げられます。

FactoryBot.lintのお勧めの使い方はテストスートが実行される前に個別のタスクでこれを走らせることです。 before(:suite)で走らせるとテスト単体を走らせるときにテストの効率に直に打撃があります。

Rakeタスクの例は以下です。

# lib/tasks/factory_bot.rake
namespace :factory_bot do
  desc "Verify that all FactoryBot factories are valid"
  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

FactoryBot.lintを読んだ後、恐らくデータベースを整頓したいことでしょう。 レコードが作成されていることでしょうから。 上で与えられた例ではSQLトランザクションを使ってロールバックし、データベースが綺麗なままにします。

リントしたいファクトリのみを選んで渡してファクトリをリントできます。

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

FactoryBot.lint factories_to_lint

こうするとold_が接頭辞にない全てのファクトリがリントされます。

トレイトもリントされます。 このオプションは各ファクトリの全トレイトが有効なオブジェクトを生成することを自動で検証します。 これはlintメソッドにtraits: trueを渡すと有効になります。

FactoryBot.lint traits: true

これは他の実引数とも組み合わせられます。

FactoryBot.lint factories_to_lint, traits: true

リントに使われる戦略を指定することもできます。

FactoryBot.lint strategy: :build

冗長なリントは各エラーに完全なバックトレースを含めます。 デバッグで役に立つことがあります。

FactoryBot.lint verbose: true

独自の構築

factory_botを使って、initializeに渡される属性があるオブジェクトを構築したいときや、クラスを構築する上で単にnewを呼ぶ以上のことをしたいときは、ファクトリにinitialize_withを定義して既定の挙動を上塗りできます。 例えば以下です。

# user.rb
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と最大限の互換性があるよう、既定の初期化器はクラスを構築するのに実引数なしで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)
# ……とすると以下が実行されます。
User.new('value')

これは重複する代入を防止しています。 4.0より前のfactory_botでは以下が走っていました。

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

    name { 'value' }
  end
end

build(:user)
# ……とすると以下が実行されます。
user = User.new('value')
user.name = 'value'

独自の戦略

独自の構築の戦略を加えてfactory_botの挙動を拡張したいときがあります。

戦略は2つのメソッドassociation及びresultを定義します。 associationFactoryBot::FactoryRunnerインスタンスを受け取ります。 このインスタンスはrunを呼んでお好みで戦略を上塗りできます。 2つ目のメソッドresultFactoryBot::Evaluationインスタンスを受け取ります。 コールバック、objecthash(結果のインスタンスやファクトリで定義された属性に基づくハッシュを得るためのもの)、createのきっかけとなる手段を(notifyで)を提供します。 createはファクトリで定義されたto_createコールバックを実行します。

factory_botで、戦略が内部で使われる仕組みを理解するには、4つの既定の戦略それぞれのソースを眺めるのが、恐らく一番簡単です。

以下はFactoryBot::Strategy::Createを使ってモデルにJSON表現を構築する戦略を組む例です。

class JsonStrategy
  def initialize
    @strategy = FactoryBot.strategy_by_name(:create).new
  end

  delegate :association, to: :@strategy

  def result(evaluation)
    @strategy.result(evaluation).to_json
  end

  def to_sym
    :json
  end
end

factory_botに新しい戦略を認識させるために、その戦略を登録できます。

FactoryBot.register_strategy(:json, JsonStrategy)

こうして呼べるようになります。

FactoryBot.json(:user)

最後に、戦略の代わりに新しいオブジェクトと登録することによってfactory_bot独自の戦略を上塗りできます。

独自コールバック

独自の戦略を使いたいとき、独自コールバックを定義できます。

class JsonStrategy
  def initialize
    @strategy = FactoryBot.strategy_by_name(:create).new
  end

  delegate :association, to: :@strategy

  def result(evaluation)
    result = @strategy.result(evaluation)
    evaluation.notify(:before_json, result)

    result.to_json.tap do |json|
      evaluation.notify(:after_json, json)
      evaluation.notify(:make_json_awesome, json)
    end
  end

  def to_sym
    :json
  end
end

FactoryBot.register_strategy(:json, JsonStrategy)

FactoryBot.define do
  factory :user do
    before(:json)                { |user| do_something_to(user) }
    after(:json)                 { |user_json| do_something_to(user_json) }
    callback(:make_json_awesome) { |user_json| do_something_to(user_json) }
  end
end

オブジェクトを永続化するための独自メソッド

既定で、レコードを作成するとインスタンスにsave!を呼びます。 これは常に最適ではないことがあるため、ファクトリに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の計装

何のファクトリが作られたか(またどの構築戦略か)把握する目的で、コンパイルされて走るファクトリを購読する方法を提供するためにActiveSupport::Notificationsがincludeされています。 一例としては実行時間の閾値に基づいてファクトリを把握することです。

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

別の例として、ファクトリが一緒にコンパイルされる属性とトレイトを追跡することが関係します。 RSpecを使っているとき、before(:suite)及びafter(:suite)ブロックを加えてfactory_bot.compile_factoryの通知を購読できます。

factory_bot_results = {}
config.before(:suite) do
  ActiveSupport::Notifications.subscribe("factory_bot.compile_factory") do |name, start, finish, id, payload|
    factory_name = payload[:name]
    factory_class = payload[:class]
    attributes = payload[:attributes]
    traits = payload[:traits]
    factory_bot_results[factory_class] ||= {}
    factory_bot_results[factory_class][factory_name] = {
      attributes: attributes.map(&:name)
      traits: traits.map(&:name)
    }
  end
end

config.after(:suite) do
  puts factory_bot_results
end

has_many関連

has_manyの関係でデータを生成するにはいくつか方法があります。 最も単純な方法は素のRubyで補助メソッドを書いて異なるレコードと結び付けることです。

FactoryBot.define do
  factory :post do
    title { "Through the Looking Glass" }
    user
  end

  factory :user do
    name { "Rachel Sanchez" }
  end
end

def user_with_posts(posts_count: 5)
  FactoryBot.create(:user) do |user|
    FactoryBot.create_list(:post, posts_count, user: user)
  end
end

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

オブジェクトの作成を完全にfactory_botに留める方が好みであれば、after(:create)コールバック内で記事を構築できます。

FactoryBot.define do
  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, context|
        create_list(:post, context.posts_count, user: user)

        # ここでレコードを再読込する必要があるかもしれません。
        # アプリケーションに依ります。
        user.reload
      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

以下はデータベースへの保存せずに済ませる単純な例です。 buildbuild_stubbedcreateで機能します(ただしattributes_forでは上手くいきません)。 行内関連が使えます。

FactoryBot.define do
  factory :post do
    title { "Through the Looking Glass" }
    user
  end

  factory :user do
    name { "Taylor Kim" }

    factory :user_with_posts do
      posts { [association(:post)] }
    end

    # または

    trait :with_posts do
      posts { [association(:post)] }
    end
  end
end

create(:user).posts.length # 0
create(:user_with_posts).posts.length # 1
build(:user_with_posts).posts.length # 1
build_stubbed(:user_with_posts).posts.length # 1

柔軟性のため、これをコールバックの例のposts_count一過的属性と組み合わせられます。

FactoryBot.define do
  factory :post do
    title { "Through the Looking Glass" }
    user
  end

  factory :user do
    name { "Adiza Kumato" }

    factory :user_with_posts do
      transient do
        posts_count { 5 }
      end

      posts do
        Array.new(posts_count) { association(:post) }
      end
    end
  end
end

create(:user_with_posts).posts.length # 5
create(:user_with_posts, posts_count: 15).posts.length # 15
build(:user_with_posts, posts_count: 15).posts.length # 15
build_stubbed(:user_with_posts, posts_count: 15).posts.length # 15

has_and_belongs_to_many関連

has_and_belongs_to_manyの関係でデータを生成するのは前述のhas_manyの関係とよく似ています。 属性名の単数形のバージョンへの単一のオブジェクトではなく、モデルの複数形の属性名にオブジェクトの配列を渡す必要があります。

def profile_with_languages(languages_count: 2)
  FactoryBot.create(:profile) do |profile|
    FactoryBot.create_list(:language, languages_count, profiles: [profile])
  end
end

もしくはコールバックの方法では以下となります。

factory :profile_with_languages do
  transient do
    languages_count { 2 }
  end

  after(:create) do |profile, context|
    create_list(:language, context.languages_count, profiles: [profile])
    profile.reload
  end
end

あるいは行内関連の方法では以下です(なおここでのinstanceの利用は、構築されるプロファイルへの参照です)。

factory :profile_with_languages do
  transient do
    languages_count { 2 }
  end

  languages do
    Array.new(languages_count) do
      association(:language, profiles: [instance])
    end
  end
end

多相関連

多相関連はトレイトで制御できます。

FactoryBot.define do
  factory :video
  factory :photo

  factory :comment do
    for_photo # 何も指定されなければ既定で:for_photoトレイトです。

    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)

相互接続関連

オブジェクトが相互接続する方法は際限なくあり、factory_botは常にそうした関係を扱うのに向いていないかもしれません。 factory_botを使って個々のオブジェクトを構築し、素のRubyで補助メソッドを書いてこうしたオブジェクトを結び付けるのが理に適っている場合があります。

つまり、より複雑で相互接続された関係は、行内関連と構築されるinstanceへの参照とを使ってfactory_botで構築できるということです。

モデルが以下のようなものであるとします。 ここで紐付くStudentProfileは両方とも同じSchoolに属します。

class Student < ApplicationRecord
  belongs_to :school
  has_one :profile
end

class Profile < ApplicationRecord
  belongs_to :school
  belongs_to :student
end

class School < ApplicationRecord
  has_many :students
  has_many :profiles
end

生徒とプロファイルが相互に接続し、同じ学校にあることを以下のようなファクトリで確かめられます。

FactoryBot.define do
  factory :student do
    school
    profile { association :profile, student: instance, school: school }
  end

  factory :profile do
    school
    student { association :student, profile: instance, school: school }
  end

  factory :school
end

なおこの方法はbuildbuild_stubbedcreateで上手くいきます。 ただしattributes_forを使うと紐付けからnilが返ります。

また、独自のinitialize_with内で属性を代入したとき(例えばinitialize_with { new(**attributes) })、これらの属性はinstanceを参照すべきではないことにも注意してください。 nilになるからです。

factory_bot ビルド状態 Code Climate Gemのバージョン

factory_botはフィクスチャの代わりになるものです。 直感的な定義構文であり、複数の構築戦略(保存されたインスタンス、保存されないインスタンス、属性ハッシュ、スタブ化オブジェクト)、ファクトリ継承を含む同じクラスに対する複数のファクトリ(user、admin_user、など)に対応しています。

Railsでfactory_botを使いたいときはfactory_bot_railsを参照してください。

プロジェクト名の歴史に関心がありますか

factory_girlから移行されますか

手引きをご確認ください。

ドキュメント

factory_botの本で網羅的な参照、手引き、レシピを参照してください。

RSpecやRailsといったサードパーティライブラリの統合についての情報はfactory_botのウィキを参照してください。

詳細な導入のための映像もあります。 Upcaseで無料で見られます。

インストール

以下を走らせてください。

bundle add factory_bot

シェルで手動でgemをインストールするには、以下を走らせてください。

gem install factory_bot

対応しているRubyのバージョン

対応しているRubyのバージョンは.github/workflows/build.ymlに一覧になっています。

その他の情報

有用なツール

貢献

CONTRIBUTING.mdを参照してください。

factory_botは元はJoe Ferrisにより書かれ、thoughtbotにより保守されています。 多くの向上と不具合の修正がオープンソースコミュニティにより貢献されました。

使用許諾

factory_bot is Copyright © 2008 Joe Ferris and thoughtbot. It is free software, and may be redistributed under the terms specified in the LICENSE file.

thoughtbotについて

thoughtbot

このリポジトリはthoughtbot, inc.により保守され、資金提供されています。 thoughtbotの名前とロゴはthoughtbot, inc.の商標です。

私達はオープンソースソフトウェアが大好きです! 私達の他のプロジェクトを参照してください。 求人を募集しています

お知らせ

6.5.4(2025年6月13日)

  • ファクトリ内のmethod_missingを通じてdefinitionという名前の利用者が定義したメソッドを設定できない不具合を修正しました。 (CodeMeister)

6.5.3(2025年6月2日)

  • 修正:ブロックのないファクトリ系列 (CodeMeister)
  • 追加:系列の設定、生成、巻き戻しのメソッドを新規追加 (CodeMeister)

6.5.2(2025年5月20日)

* 変更:「冗長なリント」のテストを更新し、Ruby 3.4で変更されたバックトレースを受け付けるようにしました (CodeMeister)
* 修正:`build_stubbed`で`created_at`と`updated_at`に同じ時間記録を設定するようにしました (Kim
  Emmanuel)
* 修正:系列を改修し、複製されたトレイトが親系列を確実に使うようにしました。 (CodeMeister)
* 文書:definition_file_paths commentを修正 (Milo Winningham)
* 文書:README.mdの有用なツールの節にruby-lsp拡張を追加 (johansenja)
* 文書:定義ファイルのパスについての文書を修正 (Ryo Nakamura)
* 文書:has_many-associations.mdを更新し、トレイトで行内関連を使えることに言及しました (Matthew
  Zagaja)
* 文書:「Factory Girlからの移行」の手引きのリンクを修正 (Neil Carvalho)

6.5.1(2025年1月31日)

  • 変更:可能な場合、ActiveRecordのトランザクション中にリント検査を実行 (Sean Doyle)
  • 修正:コンパイル時間を計測するときに無作為にテストが失敗していた点 (CodeMeinster)
  • 修正:activesupportの最小要件バージョンを6.1に引き上げました (Earlopain)
  • 内部的:開発依存関係を更新 (Neil Carvalho)

6.5.0(2024年9月6日)

  • @elasticspoonにより、 https://github.com/thoughtbot/factory_bot/pull/1623 でref/factory.mdの壊れていたリンクが修正されました。 関連するイシューは1621です
  • @ydah により、 https://github.com/thoughtbot/factory_bot/pull/1625 に標準設定が追加されました
  • @stefannibrasilにより、 https://github.com/thoughtbot/factory_bot/pull/1628 でdynamic-readmeという、再利用のためのワークフローを呼ぶようにしました
  • @stefannibrasilにより、 https://github.com/thoughtbot/factory_bot/pull/1630 で再びreadmeが更新されました
  • @stefannibrasilによる https://github.com/thoughtbot/factory_bot/pull/1635 で、READMEが更新されたときにのみ、ワークフローを走らせるようにしました
  • 自動生成:@github-actionsによる https://github.com/thoughtbot/factory_bot/pull/1637 で、READMEのDynamic Sectionを更新しました
  • @m-gizziによる https://github.com/thoughtbot/factory_bot/pull/1642 で、文字列として渡された、下線文字のあるクラス名を扱う場合を、build_classに追加しました
  • @berkosにより、 https://github.com/thoughtbot/factory_bot/pull/1615 でRuby 3.3がCIに追加されました
  • @smabosheにより、 https://github.com/thoughtbot/factory_bot/pull/1655 でDependabotの構成が更新されました
  • @sarahraqueldにより、 https://github.com/thoughtbot/factory_bot/pull/1651 で新しいメンテナがCODEOWNERSに追加されました
  • @sarahraqueldによる https://github.com/thoughtbot/factory_bot/pull/1666 で、文書の書式とファイル名の競合を改善しました
  • @sarahraqueldによる https://github.com/thoughtbot/factory_bot/pull/1677 で、動的なセキュリティワークフローと SECURITY.md ファイルを追加しました
  • 自動生成:@github-actionsによる https://github.com/thoughtbot/factory_bot/pull/1678 で、SECURITY中の動的な節を更新しました
  • @Earlopainにより、 https://github.com/thoughtbot/factory_bot/pull/1686 でrails 7.2の互換性が確保されました
  • @ddieulivolにより、 https://github.com/thoughtbot/factory_bot/pull/1688 でトレイトの文書にあるファクトリ定義が修正されました

6.4.6(2023年1月30日)

  • 修正:gemspecのRubyの最小バージョンの要件を上げました (Earlopain)。
  • 修正:FactoryBot.modifyのリンク切れを直しました (Matt Brictson)。
  • 修正:FactoryBot.lintのリンク切れを直しました (Anton Dieterle)。

6.4.5(2023年12月29日)

  • 変更:Ruby 3.0以降、Rails6.1以降に対応しました (Mike Burns)。

6.4.4(2023年12月27日)

  • 内部的:observerの依存関係を除きました (Earlopain)。

6.4.3(2023年12月26日)

  • 修正:build_stubbedでIDセッターのないモデルに対応しました (Olivier Bellone)。
  • 修正:明示的な観察器の依存 (Oleg Antonyan)。
  • 内部:Rails 7.1をCIに追加 (Olivier Bellone)。
  • 内部:githubのactions/checkoutをv4に更新 (Lorenzo Zabot)。
  • 内部:CIでdisable-error_highlightを渡すのを中止 (Mike Burns)。
  • 内部:例外伝文検査を緩和 (Mike Burns)。

6.4.2(2023年11月22日)

  • 修正:最上水準のトレイトで、クラスがActiveSupport::Notificationsに渡されるようにしました (makicamel)。

6.4.1(2023年11月20日)

  • 修正:トレイトのあるファクトリについて、そのクラスがActiveSupport::Notificationsに渡されるようになりました (makicamel)。

6.4.0 (November 17, 2023)

  • Added: if build_stubbed detects a UUID primary key, generate the correct type (Peter Boling, Alexandre Ruban).
  • Docs: show examples of Ruby 3 syntactic sugars (Sean Doyle).
  • Internal: resolve test warning messages (Mike Burns).

6.3.0 (September 1, 2023)

  • Fix: link to changelog for RubyGems (Berkan Ünal).
  • Fix: integrate with Ruby 3.2's did_you_mean library (Daniel Colson).
  • Changed: explicitly define #destroyed? within the Stub strategy to return false to be consistent with ActiveRecord (Benjamin Fleischer).
  • Added: announce factory_bot.compile_factory notification (Sean Doyle).
  • Docs: clarify that custom strategies need to define #to_sym (Edmund Korley, Jonas S).
  • Docs: fix CI link in README (Mark Huk).
  • Docs: fix GitHub links (Robert Fletcher).
  • Docs: install this library with bundle add (Glauco Custódio).
  • Docs: re-write into mdBook (Mike Burns, Sara Jackson, Stefanni Brasil)
  • Docs: clarify that automatic trait definitions could introduce new linting errors (Lawrence Chou).
  • Internal: skip TruffleRuby on Rails 5.0, 5.1, 5.2 (Andrii Konchyn).
  • Internal: fix typoes throughout codebase (Yudai Takada).
  • Internal: run CI on actions/checkout v3 (Yudai Takada).
  • Internal: follow standardrb code style (Yudai Takada).
  • Internal: stop using Hound (Daniel Nolan).
  • Internal: only run simplecov on C Ruby (Daniel Colson).
  • Internal: quieter Cucumber (Daniel Colson).
  • Internal: Ruby 3.2 support (Daniel Colson).
  • Internal: Mike Burns is the CODEOWNER (Stefanni Brasil).

6.2.1 (March 8, 2022)

  • Added: CI testing against truffleruby
  • Changed: Documentation improvements for sequences and traits
  • Fixed: ActiveSupport::Notifications reporting strategy through associations now report as symbols
    • BREAKING CHANGE: Custom strategies now need to define a to_sym method to specify the strategy identifier
  • Fixed: add_attribute with reserved keywords assigns values correctly

6.2.0 (May 7, 2021)

  • Added: support for Ruby 3.0
  • Changed: Include factory or trait name in error messages for missing traits. d05a9a3c
  • Changed: Switched from Travis CI to GitHub Actions
  • Fixed: More Ruby 2.7 kwarg deprecation warnings

6.1.0 (July 8, 2020)

  • Added: public reader for the evaluation instance, helpful for building interrelated associations
  • Changed: raise a more helpful error when passing an invalid argument to an association
  • Fixed: Ruby 2.7 kwarg deprecation warnings

6.0.2 (June 19, 2020)

  • Fixed: bug causing traits to consume more memory each time they were used

6.0.1 (June 19, 2020)

  • Fixed: bug with constant resolution causing unexpected uninitialized constant errors

6.0.0 (June 18, 2020)

  • Added: automatic definition of traits for Active Record enum attributes, enabled by default (Note that this required changing where factory_bot constantizes the build class, which may affect applications that were using abstract factories for inheritance. See issue #1409.) (This may break FactoryBot.lint because there may be previously non-existing factory+trait combinations being defined and checked)
  • Added: traits_for_enum method to define traits for non-Active Record enums
  • Added: build_stubbed_starting_id= option to define the starting id for build_stubbed
  • Removed: deprecated methods on the top-level FactoryBot module meant only for internal use
  • Removed: support for EOL versions of Ruby (2.3, 2.4) and Rails (4.2)
  • Removed: support for "abstract" factories with no associated class; use traits instead.

5.2.0 (April 24, 2020)

  • Added: Pass index to block for *_list methods
  • Deprecated: methods on the top-level FactoryBot module meant only for internal use: callbacks, configuration, constructor, initialize_with, register_sequence, resent_configuration, skip_create, to_create

5.1.2 (March 25, 2020)

  • Fixed: Ruby 2.7 keyword deprecation warning in FactoryBot.lint

5.1.1 (October 2, 2019)

  • Improved: performance of traits
  • Fixed: registering strategies on JRuby

5.1.0 (September 21, 2019)

  • Added: "Did you mean?" style error message to help with typos in association declarations
  • Changed: NoMethodError for static attributes now offers a "Did you mean?" style message
  • Fixed: avoid undefining inherited evaluator methods
  • Fixed: avoid stubbing id for records without a primary key
  • Fixed: raise a helpful error for self-referencing traits to avoid a SystemStackError
  • Deprecated: methods on the top-level FactoryBot module meant only for internal use: allow_class_lookup, allow_class_lookup=, register_trait, trait_by_name, traits, sequence_by_name, sequences, factory_by_name, register_factory, callback_names, register_callback, register_default_callbacks, register_default_strategies, strategies

5.0.2 (February 22, 2019)

  • Bugfix: raise "Trait not registered" error when passing invalid trait arguments

5.0.1 (February 15, 2019)

  • Bugfix: Do not raise error when two sequences have the same name in two traits that have the same name

5.0.0 (February 1, 2019)

  • Added: Verbose option to include full backtraces in the linting output
  • Changed: use_parent_strategy now defaults to true, so by default the build strategy will build, rather than create associations
  • Changed: Passing a block when defining associations now raises an error
  • Bugfix: use_parent_strategy is no longer reset by FactoryBot.reload
  • Bugfix: rewind_sequences will now rewind local sequences along with the global ones
  • Bugfix: the build_stubbed strategy now sets timestamps without changing the the original behavior of the timestamp methods
  • Bugfix: avoid a stack error when referring to an "attributes" attribute in initialize_with
  • Removed: support for EOL versions of Ruby and Rails
  • Removed: static attributes (use dynamic attributes with a block instead)
  • Removed: looking up factories by class
  • Removed: ignore method (use transient instead)
  • Removed: duplicate_attribute_assignment_from_initialize_with configuration option
  • Deprecated: allow_class_lookup configuration option

4.11.1 (September 7, 2018)

  • Documentation: Include .yardopts in the gem to fix broken RubyDoc links

4.11.0 (August, 15, 2018)

  • Bugfix: Do not raise error for valid build_stubbed methods: decrement, increment, and toggle
  • Bugfix: Do not add timestamps with build_stubbed for objects that shouldn't have timestamps
  • 静的属性を廃止しました。

4.10.0 (May 25, 2018)

  • Allow sequences to be rewound

4.9.0 (skipped - FactoryGirl only release)

4.8.2 (October 20, 2017)

  • Rename factory_girl to factory_bot

4.8.1 (September 28, 2017)

  • Explicitly define #destroyed? within the Stub strategy to return nil instead of raising
  • Update various dependencies
  • Update internal test suite to use RSpec's mocking/stubbing instead of mocha

4.8.0 (December 16, 2016)

  • ドキュメントを改善しました。
  • Add FactoryGirl.generate_list to be consistent with build_list/create_list and friends
  • Add FactoryGirl.use_parent_strategy configuration to allow associations to leverage parent build strategy

4.7.0 (April 1, 2016)

  • ドキュメントを改善しました。
  • Improve instrumentation payload to include traits, overrides, and the factory itself
  • トレイトをリントできるようにしました。
  • Deprecate factory lookup by class name in preparation for 5.0
  • Improve internal performance by using flat_map instead of map and compact
  • Improve handling of dirty attributes after building a stubbed object
  • Reduce warnings from redefining methods

4.6.0 (skipped)

4.5.0 (October 17, 2014)

  • Improve FactoryGirl.lint by including exception and message in output
  • Allow selective linting
  • Use more explicit #public_send when doing attribute assignment
  • Improve documentation around FactoryGirl.lint and initialize_with
  • Deprecate #ignore in favor of #transient

4.4.0 (February 10, 2014)

  • FactoryGirl.lintを加えました。
  • Fix memory leak in duplicate traits
  • ドキュメントを更新しました。

4.3.0 (November 3, 2013)

  • Start testing against Rails 4.0 and Ruby 2.0.0
  • Stop testing against Rails 3.0 and Ruby 1.9.2
  • Add *_pair methods to only build two objects
  • Raise if a method is defined with a FactoryGirl block (factory or trait)
  • Allow use of Symbol#to_proc in callbacks
  • 大域コールバックを加えました。
  • Improve GETTING_STARTED and README

4.2.0 (January 18, 2013)

  • ドキュメントを改善しました。
  • Allow *_list syntax methods to accept a block
  • Update gem dependencies
  • Allow setting id for objects created with build_stubbed
  • Fix Stub strategy to mimic ActiveRecord regarding created_at
  • Evaluate sequences within the context of an Evaluator
  • Fix Mocha deprecation warning
  • Fix some warnings when running RUBYOPT=-w rake
  • Convert test suite to RSpec's "expect" syntax

4.1.0 (September 11, 2012)

  • Allow multiple callbacks to bind to the same block
  • Fix documentation surrounding the stub strategy

4.0.0 (August 3, 2012)

  • Remove deprecated cucumber_steps
  • Remove deprecated alternate syntaxes
  • Deprecate duplicate_attribute_assignment_from_initialize_with, which is now unused as attributes assigned within initialize_with are not subsequently assigned

3.6.1 (August 2, 2012)

Update README to include info about running with JRuby

  • Update dependencies on RSpec and tiny versions of Rails in Appraisal
  • Improve flexibility of using traits with associations and add documentation
  • Stub update_column to raise to mirror ActiveRecord's change from update_attribute

3.6.0 (July 27, 2012)

  • Code/spec cleanup
  • Allow factories with traits to be used in associations
  • Refactor Factory to use DefinitionHierarchy to handle managing callbacks, custom constructor, and custom to_create
  • Add memoization to speed up factories providing attribute overrides
  • Add initial support of JRuby when running in 1.9 mode
  • Improve docs on what happens when including FactoryGirl::Syntax::Methods

3.5.0 (June 22, 2012)

  • Allow created_at to be set when using build_stubbed
  • Deprecate FactoryGirl step definitions

3.4.2 (June 19, 2012)

  • Fix bug in traits with callbacks called implicitly in factories whose callbacks trigger multiple times

3.4.1 (June 18, 2012)

  • Fix traits so they can be nested and referred to from other traits

3.4.0 (June 11, 2012)

  • Sequences support Enumerators
  • Optionally disable duplicate assignment of attributes in initialize_with
  • Make hash of public attributes available in initialize_with
  • Support referring to a factory based on class name

3.3.0 (May 13, 2012)

  • to_create、skip_create、initialize_withを大域的に定義できるようにしました。
  • to_create、skip_create、initialize_withをトレイト内で定義できるようにしました。
  • Fix deprecation messages for alternate syntaxes (make, generate, etc.)
  • Improve library documentation
  • Deprecate after_build, after_create, before_create, after_stub in favor of new callbacks
  • Introduce new callback syntax: after(:build) {}, after(:custom) {}, or callback(:different) {} This allows for declaring any callback, usable with custom strategies
  • Add attributes_for_list and build_stubbed_list with the StrategySyntaxMethodRegistrar
  • Allow use of syntax methods (build, create, generate, etc) implicitly in callbacks
  • Internal refactoring of a handful of components

3.2.0 (April 24, 2012)

  • Use AS::Notifications for pub/sub to track running factories
  • Call new within initialize_with implicitly on the build class
  • Skip to_create with skip_create
  • Allow registration of custom strategies
  • Deprecate alternate syntaxes
  • Implicitly call factory_bot's syntax methods from dynamic attributes

3.1.0 (April 6, 2012)

  • Sequences support aliases, which reference the same block
  • ドキュメントを更新しました。
  • before_createコールバックを加えました。
  • Support use of #attribute_names method to determine available attributes for steps
  • Use ActiveSupport::Deprecation for all deprecations

3.0.0 (March 23, 2012)

  • Deprecate the vintage syntax
  • Remove Rails 2.x support
  • Remove Ruby 1.8 support
  • Remove deprecated features, including default_strategy, factory_name, :method for defining default strategy, ignore on individual attributes, and interacting with Factory the way you would FactoryGirl

2.6.4 (March 16, 2012)

  • 一過的属性名を無視しないようにしました。
  • Ensure attributes set on instance are calculated uniquely

2.6.3 (March 9, 2012)

  • Fix issue with traits not being present the first time a factory is accessed
  • Update available Cucumber step definitions to not require a trailing colon when building a table of attributes to instantiate records with

2.6.2 (March 9, 2012)

  • Allow factories to use all their ancestors' traits
  • Ignore bin dir generated by bundler
  • Namespace ::Factory as top-level to fix vintage syntax issue with Ruby 1.9.2-p3p18

2.6.1 (March 2, 2012)

  • Use FactoryGirl.reload in specs
  • Clean up running named factories with a particular strategy with FactoryGirl::FactoryRunner

2.6.0 (February 17, 2012)

  • Improve documentation of has_many associations in the GETTING_STARTED document
  • Deprecate :method in favor of :strategy when overriding an association's build strategy

2.5.2 (February 10, 2012)

  • Fix step definitions to use associations defined in parent factories
  • Add inline trait support to (build|create)_list
  • Update ActiveSupport dependency to >= 2.3.9, which introduced class_attribute

2.5.1 (February 3, 2012)

  • Fix attribute evaluation when the attribute isn't defined in the factory but is a private method on Object
  • Update rubygems on Travis before running tests
  • Fix spec name
  • Update GETTING_STARTED with correct usage of build_stubbed
  • READMEを更新し、initialize_withについて追記しました。
  • Honor :parent on factory over block nesting

2.5.0 (January 20, 2012)

  • Revert 'Deprecate build_stubbed and attributes_for'
  • Implement initialize_with to allow overriding object instantiation
  • Ensure FG runs against Rails 3.2.0

2.4.2 (January 18, 2012)

  • 行内トレイトと既定のファクトリとの相互作用を修正しました。

2.4.1 (January 17, 2012)

  • Deprecate build_stubbed and attributes_for
  • 行内トレイトを修正しました。

2.4.0 (January 13, 2012)

  • Refactor internals of FactoryGirl to use anonymous class on which attributes get defined
  • Explicitly require Ruby 1.8.7 or higher in gemspec
  • ドキュメントを修正しました。
  • Add Gemnasium status to documentation
  • Supplying a Class to a factory that overrides to_s no longer results in getting the wrong Class constructed
  • Be more agnostic about ORMs when using columns in FactoryGirl step definitions
  • Test against Active Record 3.2.0.rc2
  • Update GETTING_STARTED to use Ruby syntax highlighting

2.3.2 (November 26, 2011)

  • Move logic of where instance.save! is set to Definition
  • Fix method name from aliases_for? to alias_for?
  • Refactor internal attribute handling to use an anonymous class instead of faking Ruby's variable resolution. This allows for more sane usage of attributes without having to manage sorting priority because attributes can turn themselves into procs, which are used with define_method on a class so attributes work correctly all the time.

2.3.1 (November 23, 2011)

  • Remove internally-used associate method from all the FactoryGirl::Proxy subclasses
  • Move around requiring of files
  • Consolidate errors into factory_bot.rb
  • Refactor AttributeList to deal with priority only when iterating over attributes
  • Refactor internals of some of the Proxy subclasses
  • Ensure callbacks on traits are executed in the correct order

2.3.0 (November 18, 2011)

  • Registries are named, resulting in better messages when factories, traits, or sequences cannot be found
  • Fix incorrect tests
  • Internals refactoring introducing FactoryGirl::NullFactory, FactoryGirl::Definition, and FactoryGirl::DeclarationList
  • Use ActiveSupport for Hash#except and its delegation capabilities
  • Fix usage of callbacks when added via implicit traits
  • Use Bundler tasks and clean up dependencies
  • Fix failing spec for big letters in factory name passed as symbol
  • Add ability for traits to be added dynamically when creating an instance via build, create, build_stubbed, or attributes_for

2.2.0 (October 14, 2011)

  • Clean up RSpec suite to not use 'should'
  • Use create_list in step definitions
  • Syntax methods that deal with ORM interaction (attributes_for, build, build_stubbed, and create) now accept a block that yields the result. This results in a more convenient way to interact with the result than using Object.tap.
  • Standardize deprecation warnings
  • Update transient attribute syntax to use blocks instead of calling ignore on each attribute declaration
  • Parents can be defined after children because factories are evaluated when they're used; this means breaking up factories across multiple files will behave as expected
  • Large internal refactoring, including changing access modifiers for a handful of methods for a more clearly defined API

2.1.2 (September 23, 2011)

  • Bugfix: Vintage syntax fixed after bug introduced in 2.1.1
  • Introduce dependency on activesupport to remove code from Factory class

2.1.1 (September 23, 2011) (yanked)

  • Bugfix: Parent object callbacks are run before child object callbacks
  • Declarations: allow overriding/modification of individual traits in child factories
  • Callbacks refactored to not be attributes
  • Updating documentation for formatting and clarity (incl. new specificity for cucumber)

2.1.0 (September 02, 2011)

  • Bugfix: created_at now defined for stubbed models
  • Gemspec updated for use with Rails 3.1
  • Factories can now be modified post-definition (useful for overriding defaults from gems/plugins)
  • All factories can now be reloaded with Factory.reload
  • Add :method => build to factory associations to prevent saving of associated objects
  • Factories defined in {Rails.root}/factories are now loaded by default
  • Various documentation updates

1.1.4 (November 28, 2008)

  • Factory.build now uses Factory.create for associations of the built object
  • Factory definitions are now detected in subdirectories, such as factories/person_factory.rb (thanks to Josh Nichols)
  • Factory definitions are now loaded after the environment in a Rails project (fixes some issues with dependencies being loaded too early) (thanks to Josh Nichols)
  • Factory names ending in 's' no longer cause problems (thanks to Alex Sharp and Josh Owens)

1.1.3 (September 12, 2008)

  • 自動的にfactories.rb、test/factories.rb、spec/factories.rbから定義を取り入れるようにしました。

1.1.2 (July 30, 2008)

  • Improved error handling for invalid and undefined factories/attributes
  • Improved handling of strings vs symbols vs classes
  • Added a prettier syntax for handling associations
  • Updated documentation and fixed compatibility with Rails 2.1

1.1.1 (June 23, 2008)

  • The attribute "name" no longer requires using #add_attribute

1.1.0 (June 03, 2008)

  • 依存属性の対応を加えました。
  • Fixed the attributes_for build strategy to not build associations
  • Added support for sequences

1.0.0 (May 31, 2008)

  • First version

プロジェクト名の歴史

Factory Girl

このライブラリは2008年に「Factory Girl」という名前で初回リリースされました。

デザインパターン本のソフトウェアパターンであるファクトリーメソッド母オブジェクトの理念に共感して名前を選びました。 また同名のローリングストーンズの歌への参照にもなっています。

Factory Bot

「Factory Girl」という名前は、このライブラリを知った開発者や攻撃的ないし問題があるように感じたりする人にとって困惑するものでした。 2017年10月に「Factory Bot」にライブラリを改名しました。

Factory Botへの貢献

どなたでもプルリクエストを歓迎します。 このプロジェクトに参加することで、thoughtbotの行動規範に従うことに同意したものとします。

以下はあなたが貢献できる方法です。

  • アルファ、ベータ、プレリリースのバージョンを使う
  • 不具合を報告する
  • 新機能を提案する
  • ドキュメントを書いたり編集したりする
  • 仕様を書く
  • コードを書く(どんなパッチも些細ではありません。誤植修正やコメントの追加などもそうです)
  • コードを改修する
  • イシューを閉じる
  • パッチをレビューする

イシューを送る

  • GitHubのイシュートラッカーを使って不具合と機能と把握しています。
  • 不具合報告や機能の要望を送る前に、既に送られていないか必ずお確かめください。
  • 不具合報告を送るとき、再現スクリプトとその不具合を再現するのに必要かもしれないその他の詳細を含めてください。 これにはgemのバージョン、Rubyのバージョン、オペレーティングシステムが含まれます。

イシューを整頓する

  • 送信者から反応がないイシューは30日以降に閉じられます。
  • イシューは修正されたり回答されたと思われたときに閉じられます。 メンテナが間違っていたときは再び開くことがあります。
  • イシューが間違って閉じられたときはイシューを理解して説明してください。 喜んでイシューを再度開きます。

プルリクエストを送る

  1. 公式リポジトリフォークします。
  2. トピックブランチを作ります
  3. 機能や不具合修正を実装します。
  4. 変更を加え、コミットし、プッシュします。
  5. プルリクエストを送る

補足

  • コードを変えたときはテストを加えてください。 テストのない貢献は受け付けられません。
  • テストの加え方が分からなければ、PRを作って手助けを求めるコメントを残してください。 喜んでお手伝いします!
  • gemのバージョンを更新しないでください。

準備

bundle install

テストスートを走らせる

既定のrakeタスクでは、完全なテストスートとstandardが実行されます。

bundle exec rake

1グループのテスト(単体、スペック、機能)を走らせることもできます。

bundle exec rake spec:unit
bundle exec rake spec:acceptance
bundle exec rake features

個別のrspecのテストを走らせるため、パスと行番号を与えられます。

bundle exec rspec spec/path/to/spec.rb:123

appraisalで特定のバージョンのrailsでテストを走らせられます。 Rails 6に対して、既定のrakeタスクを走らせる例は以下です。

bundle exec appraisal 6.0 rake

整形

自動でコードを整形するにはstandardを使ってください。

bundle exec rake standard:fix

https://github.com/middleman/middleman-heroku/blob/master/CONTRIBUTING.md から着想を得ました。

この日本語訳について

本文書は``The factory_bot book''の日本語訳です。

日本語訳のファイル(Markdown形式)は原文のファイル構造に従って生成され、translation/jaディレクトリ以下に配置されます。 特にtranslation/ja/docsディレクトリ以下に配置されるファイルはmdBookによりウェブページに変換されます。

翻訳管理にはpo4aが使われており、GNU GettextのPO形式により翻訳が保管されます。 そのため、原文に変更が発生した場合は次の手順で更新します。 なお、お知らせは過去のバージョンは未翻訳のものが残っています。 これらのバージョンは必要に応じて訳出することとします。 POファイルではtranslation/po/news.ja.poに分離されています。

  1. 原文のリポジトリに合わせてリベースする。
  2. make -C translationでPOファイルを更新する。
  3. 更新されたtranslation/po/*.ja.poを編集し、fuzzyの項目や未翻訳の項目の翻訳を完了する。
  4. 再度make -C translationを実行し、日本語訳のファイルを生成する。