Custom Construction

If you want to use factory_bot to construct an object where some attributes are passed to initialize or if you want to do something other than simply calling new on your build class, you can override the default behavior by defining initialize_with on your factory. Example:

# 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

Although factory_bot is written to work with ActiveRecord out of the box, it can also work with any Ruby class. For maximum compatibility with ActiveRecord, the default initializer builds all instances by calling new on your build class without any arguments. It then calls attribute writer methods to assign all the attribute values. While that works fine for ActiveRecord, it actually doesn't work for almost any other Ruby class.

You can override the initializer in order to:

  • Build non-ActiveRecord objects that require arguments to initialize
  • Use a method other than new to instantiate the instance
  • Do wild things like decorate the instance after it's built

When using initialize_with, you don't have to declare the class itself when calling new; however, any other class methods you want to call will have to be called on the class explicitly.

For example:

factory :user do
  name { "John Doe" }

  initialize_with { User.build_with_name(name) }
end

You can also access all public attributes within the initialize_with block by calling attributes:

factory :user do
  transient do
    comments_count { 5 }
  end

  name "John Doe"

  initialize_with { new(**attributes) }
end

This will build a hash of all attributes to be passed to new. It won't include transient attributes, but everything else defined in the factory will be passed (associations, evaluated sequences, etc.)

You can define initialize_with for all factories by including it in the FactoryBot.define block:

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

When using initialize_with, attributes accessed from within the initialize_with block are assigned only in the constructor; this equates to roughly the following code:

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

    name { 'value' }
  end
end

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

This prevents duplicate assignment; in versions of factory_bot before 4.0, it would run this:

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

    name { 'value' }
  end
end

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