public
Last active

FactoryGirl vs. Fabrication

  • Download Gist
fabrication_feature_pool_spec.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
require 'spec_helper'
 
describe FeaturePool do
it "creates a new instance given valid attributes" do
Fabricate :feature_pool
end
 
it "is not valid without a name" do
Fabricate.build(:feature_pool, :name => "").should_not be_valid
end
 
describe "#current" do
it "returns the feature appearance whose position corresponds with #head" do
pool = Fabricate :populated_feature_pool
pool.current.featurable.user.name.should == "John Gruber"
end
 
it "has some feature appearances" do
pool = Fabricate :populated_feature_pool
pool.should have(5).feature_appearances
end
 
it "persists its appearances" do
Fabricate :populated_feature_pool, :name => "foo/bar"
 
pool = FeaturePool.find_by_name("foo/bar")
pool.should have(5).feature_appearances
end
end
end
fabricators.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
Fabricator(:feature_pool) do
name "home/primary"
accepts "NewsArticle"
rotate_after 24
head 1
paused false
end
 
Fabricator(:populated_feature_pool, :from => :feature_pool) do
feature_appearances!(:count => 5) do |feature_pool, position|
Fabricate :feature_appearance, :feature_pool => feature_pool, :position => position
end
end
 
Fabricator(:feature_appearance) do
featurable! :fabricator => :news_article
position 1
end
 
Fabricator(:news_article) do
title "This Is the New Apple"
body "Superior products _and_ mainstream prices."
user!
end
 
Fabricator(:user) do
name "John Gruber"
email { sequence(:email) {|n| "user#{n}@example.com" } }
end
factories.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
FactoryGirl.define do
factory :feature_pool do
name "home/primary"
accepts "NewsArticle"
rotate_after 24
head 1
paused false
 
factory :populated_feature_pool do
feature_appearances { FactoryGirl.build_list(:feature_appearance, 5) }
end
end
 
factory :feature_appearance do
association :featurable, :factory => :news_article
position 1
end
 
factory :news_article do
title "This Is the New Apple"
body "Superior products _and_ mainstream prices."
user
end
 
factory :user do
name "John Gruber"
sequence(:email) {|n| "user#{n}@example.com" }
end
end
factory_girl_feature_pool_spec.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
require 'spec_helper'
 
describe FeaturePool do
it "creates a new instance given valid attributes" do
FactoryGirl.create(:feature_pool)
end
 
it "is not valid without a name" do
FactoryGirl.build(:feature_pool, :name => "").should_not be_valid
end
 
describe "#current" do
it "returns the feature appearance whose position corresponds with #head" do
pool = FactoryGirl.create(:populated_feature_pool)
pool.current.featurable.user.name.should == "John Gruber"
end
 
it "has some feature appearances" do
pool = FactoryGirl.create(:populated_feature_pool)
pool.should have(5).feature_appearances
end
 
it "persists its appearances" do
FactoryGirl.create(:populated_feature_pool, :name => "foo/bar")
 
pool = FeaturePool.find_by_name("foo/bar")
pool.should have(5).feature_appearances
end
end
end

Here's a comparison of the syntax required for FactoryGirl and Fabrication, using a simplified version of the feature-pool functionality in Connect. Obviously the examples here are not indicative of real tests, I wanted to get an idea of how each library worked with various situations.

My thoughts:

  • I like typing Fabricate more than FactoryGirl. Other than that, the actual object-generation calling is the same
  • I like Fabrication's syntax for handling :has_many relationships quite a bit better, since it's really easy to do the :populated_featured_pool factory and have each assigned a different position. Note that the FactoryGirl example doesn't really do this. (I'm sure we could do that with FactoryGirl, just not as elegantly).
  • FactoryGirl seems to handle relationships much more intuitively than Fabrication. The long chain pool.current.featurable.user.name worked right off the bat in FactoryGirl, but Fabrication gave me grief until I appended the bangs to the relationship methods (Fabrication's way of being told that it should set up those relationships right now instead of lazily setting those up, which doesn't work. I guess it's just a matter of getting used to it, but it was a little weird at first, and it makes me wonder if there's any use for the lazy relationships.
  • I like FactoryGirl's factory inheritance syntax a little more, I think--the embedded factory call as opposed to the :from => required by Fabrication. You might feel differently?

Thanks this is an awesome comparison!

You can include FactoryGirl::Syntax::Methods and stop typing FactoryGirl.

These examples display pretty bad practices unfortunately - you should never build up complex structure in your factories or fabrications. It gives them the same downsides as fixtures. Structure should always be built up within the test itself.

Having complex structure in factory objects to DRY your tests obscures the object structure, adds unnecessary associations for tests that don't use them, and creates problems when you need a test which changes one small association or value, and all your other tests fail. It's an example of bad DRY.

Sucks that people keep persisting it. The line:

pool.should have(5).feature_appearances

...for example, means that your test is now tied to 5 associated objects within EVERY feature pool factory by default - unseen by the test itself. All tests that don't need any feature appearances, create them anyway. Then you start building tests assuming the value will be five, overriding if not - suddenly most of your tests are overwriting the default, somebody changes it, and your tests are breaking because of a value far outside the test itself.

Obviously this is an overly simplified example that is easy to fix, but start multiplying the assumptions you're making about the structure of your data, with other associations and sub-objects, and soon your tests are unmaintainable soup.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.