Factory Bot (Factory Girl)
Factory Bot (Factory Girl)

Factory Bot (Factory Girl)

Published
Published June 8, 2021
Author
 

Using factories

Build strategies
factory_bot supports several different build strategies: build, create, attributes_for and build_stubbed:
user = build(:user) # Returns a User instance that's not saved user = create(:user) # Returns a saved User instance attrs = attributes_for(:user) # Returns a hash of attributes that can be used to build a User instance stub = build_stubbed(:user) # Returns an object with all defined attributes stubbed out # Passing a block to any of the methods above will yield the return object create(:user) do |user| user.posts.create(attributes_for(:post)) end
Attribute overrides
No matter which strategy is used, it's possible to override the defined attributes by passing a hash:
user = build(:user, first_name: "Joe")
build_stubbed and Marshal.dump
Note that objects created with build_stubbed cannot be serialized with Marshal.dump, since factory_bot defines singleton methods on these objects.

Dependent Attributes

Attributes can be based on the values of other attributes using the evaluator that is yielded to dynamic attribute blocks:
factory :user do first_name { "Joe" } last_name { "Blow" } email { "#{first_name}.#{last_name}@example.com".downcase } end

Aliases

factory_bot allows you to define aliases to existing factories to make them easier to re-use. This could come in handy when, for example, your Post object has an author attribute that actually refers to an instance of a User class. While normally factory_bot can infer the factory name from the association name, in this case it will look for an author factory in vain. So, alias your user factory so it can be used under alias names.
factory :user, aliases: [:author, :commenter] do first_name { "John" } last_name { "Doe" } date_of_birth { 18.years.ago } end factory :post do author # The alias allows us to write 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 # The alias allows us to write commenter instead of association :commenter, factory: :user body { "Great article!" } end
 

Transient Attributes

Transient attributes are attributes only available within the factory definition, and not set on the object being built. This allows for more complex logic inside factories.
With other attributes
There may be times where your code can be DRYed up by passing in transient attributes to factories. You can access transient attributes within other attributes (see Dependent Attributes):
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"
With attributes_for
Transient attributes will be ignored within attributes_for and won't be set on the model, even if the attribute exists or you attempt to override it.
With callbacks
If you need to access the evaluator in a factory_bot callback, you'll need to declare a second block argument (for the evaluator) and access transient attributes from there.
factory :user do transient do upcased { false } end name { "John Doe" } after(:create) do |user, evaluator| user.name.upcase! if evaluator.upcased end end create(:user).name #=> "John Doe" create(:user, upcased: true).name #=> "JOHN DOE"
With associations
Transient associations are not supported in factory_bot. Associations within the transient block will be treated as regular, non-transient associations.
If needed, you can generally work around this by building a factory within a transient attribute:
factory :post factory :user do transient do post { build(:post) } end end

Associations

Definition
factory :post do author # 隐式定义 Implicit definition association :author # 显示定义 Explicit definition author {association :author} # 内联定义 Inline definition # Specifying the factory author factory: :user # Implicitly association :author, factory: :user # Explicitly author { association :user } # Inline end
has_many / has_and_belongs_to_many associations
use after(:create) callback.
FactoryBot.define do factory :post do title { "Through the Looking Glass" } user end factory :user do name { "John Doe" } factory :user_with_posts do transient { posts_count { 5 } } after(:create) do |user, evaluator| create_list(:post, evaluator.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
use inline associations:
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
Polymorphic associations
Polymorphic associations can be handled with traits:
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)
Interconnected associations
互相关联的关系,可以用 inline associations 和 instance
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

Sequence

自增长属性
# Global sequence FactoryBot.define do sequence :email {|n| "person#{n}@example.com"} end generate :email # => "person1@example.com" generate :email # => "person2@example.com" FactoryBot.rewind_sequences # Sequences can also be rewound generate(:email) # "person1@example.com" factory :user do email { generate(:email) } # used in dynamic attributes email # implicit attributes. Same as ditto sequence(:email) { |n| "person#{n}@example.com" } # Inline sequences sequence(:email, 1000) { |n| "person#{n}@example.com" } # Initial value sequence(:email, aliases: [:sender]) { |n| "person#{n}@example.com" } # aliases end

Traits

Traits allow you to group attributes together and then apply them to any factory.
factory :user, aliases: [:author] factory :story do title { "My awesome story" } author trait :published { published { true } end trait :week_long do start_at { 1.week.ago } end factory :week_long_published_story, traits: [:published, :week_long] end # implicit attributes, Traits can be used as implicit attributes: factory :week_long_published_story_with_title, parent: :story do published week_long title { "Publishing that was started at #{start_at}" } end # using traits create(:user, :admin, :male, name: "Jon Snow")
Attribute precedence
Traits that define the same attributes won't raise AttributeDefinitionErrors; the trait that defines the attribute latest gets precedence.
factory :user do name { "Friendly User" } login { name } trait :male do name { "John Doe" } gender { "Male" } end trait :admin do admin { true } login { "admin-#{name}" } end factory :male_admin, traits: [:male, :admin] # login will be "admin-John Doe" end

Inheritance

Nested factories
You can easily create multiple factories for the same class without repeating common attributes by nesting factories:
factory :post do title { "A title" } factory :approved_post do approved { true } end end
Assigning parent explicitly
You can also assign the parent explicitly:
factory :post do title { "A title" } end factory :approved_post, parent: :post do approved { true } end
Best practices
As mentioned above, it's good practice to define a basic factory for each class with only the attributes required to create it. Then, create more specific factories that inherit from this basic parent. Factory definitions are still code, so keep them DRY.

Callbacks

Default callbacks
  • after(:build) - (called by FactoryBot.buildFactoryBot.create)
  • before(:create) - (called by FactoryBot.create)
  • after(:create) - (called by FactoryBot.create)
  • after(:stub) - (called by FactoryBot.build_stubbed)
Multiple callbacks
多个callback 按顺序执行
factory :user do after(:create) { this_runs_first } after(:create) { then_this } callback(:after_stub, :before_create) { do_something } end
Global callbacks
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