FactoryBot 3.3.0 was released this weekend with a slew of improvements.
To install, add (or change) your Gemfile:
gem 'factory_bot_rails', '~> 3.3.0'
New Callback Syntax
Callbacks have been revamped to work well in conjunction with custom strategies. Instead of declaring callbacks like this:
FactoryBot.define do
factory :user do
factory :user_with_posts do
after(:create) {|instance| create_list(:post, 5, user: instance) }
end
end
end
you can declare callbacks with before
and after
, passing the symbol of
the callback as the name:
FactoryBot.define do
factory :user do
after(:custom) {|instance| instance.do_something_custom! }
factory :user_with_posts do
after(:create) {|instance| create_list(:post, 5, user: instance) }
end
end
end
Finally, you can use completely custom callbacks without a before or after
prepended by just calling callback
:
FactoryBot.define do
factory :user do
callback(:custom_callback) {|instance| instance.do_something_custom! }
end
end
These work great with custom strategies:
class CustomStrategy
def initialize
@strategy = FactoryBot.strategy_by_name(:create).new
end
delegate :association, to: :@strategy
def result(evaluation)
@strategy.result(evaluation).tap do |instance|
evaluation.notify(:custom_callback, instance) # runs callback(:custom_callback)
evaluation.notify(:after_custom, instance) # runs after(:custom)
end
end
end
Support all *_list
methods
FactoryBot already introduced build_list
and create_list
to build and create
an array of instances; in 3.3.0, *_list
methods are generated dynamically
for all strategies registered, so build_stubbed_list
and
attributes_for_list
join the immediate roster of methods; if you were to
register a strategy named “insert”, insert_list
would exist as well.
Fix to_create
and initialize_with
within traits
Traits are a great way to name an abstract concept of attributes, but for a
long time, they didn’t support defining to_create
or initialize_with
.
3.3.0 fixes this shortcoming by having to_create
and initialize_with
behave in traits exactly as you’d expect. This is perfect for decorating
objects from within FactoryBot.
class NotifierDecorator < BasicObject
undef_method :==
def initialize(component)
@component = component
end
def save!
@component.save!.tap do
Notifier.new(@component).notify("saved!")
end
end
def method_missing(name, *args, &block)
@component.send(name, *args, &block)
end
def send(symbol, *args)
__send__(symbol, *args)
end
end
FactoryBot.define do
trait :with_notifications do
to_create {|instance| NotifierDecorator.new(instance).save! }
end
factory :user
end
create(:user, :with_notifications) # decorates save! when the instance is created
FactoryBot.define do
trait :with_notifications do
initialize_with { NotifierDecorator.new(new) }
end
factory :post
end
create(:post, :with_notifications) # returns a post instance decorated with NotifierDecorator
Define to_create
and initialize_with
globally
If you’re using an ORM other than ActiveRecord, you may want to call different
methods for persistence. Declaring a to_create
(or initialize_with
, if you
wanted to use a global decorator) within the FactoryBot.define
block will
now apply to all declared factories, behaving much like sequences, traits, and
factories.
You can override the global to_create
or initialize_with
with traits or by
defining to_create
in a factory explicitly.
FactoryBot.define do
to_create {|instance| instance.persist! }
factory :user do
factory :user_backed_by_active_record do
to_create {|instance| instance.save! }
end
end
end
What’s next
There are still cases where traits don’t behave correctly (using implicit
traits is a big remaining bug) and more work for initialize_with
and
accessing attributes needs to be done.
Disclaimer:
Looking for FactoryGirl? The library was renamed in 2017. Project name history can be found here.