Factory Bot now has callbacks thanks to Nate Sutton.
There are three callbacks:
after(:build)
after(:create)
after(:stub)
When build(:user)
is invoked, the after(:build)
callback runs after building
the user. Likewise for create
and stub
.
These come in handy in a number of common use cases.
Basic has many associations
Models:
class Article < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :article
end
Factories:
factory :article do
body { 'password' }
factory :article_with_comment do
after(:create) do |article|
create(:comment, article: article)
end
end
end
factory :comment do
body { 'Great article!' }
end
Nice. Callbacks let us do this:
article = create(:article_with_comment)
Instead of this:
article = create(:article)
create(:comment, article: article)
Polymorphic relationships
The savings get larger when the object graph gets more complex:
Models:
class User < ActiveRecord::Base
has_many :interests, as: :interested
has_many :topics, through: :interests
end
class Interest < ActiveRecord::Base
belongs_to :topic
belongs_to :interested, polymorphic: true
end
Building block factories:
factory :user
email
password { 'password' }
factory :email_confirmed_user do
email_confirmed { true }
end
end
factory :topic do
name { 'topic_name' }
end
factory :interest
topic
interested factory: :user
end
factory :music_interest, class: 'Interest' do
topic.association(:topic, name: 'Music')
end
factory :sports_interest, class: 'Interest' do
topic.association(:topic, name: 'Sports')
end
And now the payoff:
factory :musical_user, parent: :email_confirmed_user do
after(:create) { |user| create(:music_interest, interested: user)
end
factory :sporty_user, parent: :email_confirmed_user do
after(:create) { |user| create(:sports_interest, interested: user) }
end
Again, factories let us do this:
user = create(:musical_user)
Instead of:
user = create(:email_confirmed_user)
create(:music_interest, interested: user)
More intention-revealing. More encapsulation, protecting us from change.
Working with fakes
Fakes are a good approach for testing objects that interface with web services such as geocoding and payment processing.
class User < ActiveRecord::Base
acts_as_mappable
before_validation :geocode_location, if: :location_changed?
def geocode_location
geo = Geokit::Geocoders::MultiGeocoder.geocode(location)
self.lat, self.lng = geo.lat, geo.lng
end
end
factory :user do
factory :boston_user do
location { 'Boston, MA' }
after(:build) do |user|
Geokit::Geocoders::FakeGeocoder.locations['Boston, MA'] = [0, 1]
end
end
end
Enjoy
Install:
gem 'factory_bot'
Happy testing.
Disclaimer:
Looking for FactoryGirl? The library was renamed in 2017. Project name history can be found here.