Aside from a handful of internal code clean up, Factory Girl 2.2 brings a handful of awesomeness to the table. Factory Girl 2.1 was released a mere month and a half ago but loads of improvements have been added since then. Some key features:
When we introduced traits, there were a couple of caveats: you couldn’t
override traits’ attributes from child factories, and you couldn’t overwrite
traits’ attributes when using FactoryGirl.modify. Not the case anymore!
We’ve been hammering away at Factory Girl’s internals, cleaning things up and
delaying factory resolution as late as possible. This allows for:
FactoryGirl.define do
factory :user do
name "Stranger"
trait :male do
name "Jon Snow"
gender "Male"
end
factory :male_user do
male
end
factory :brandon do
male
name "Brandon"
end
end
end
FactoryGirl.modify do
factory :brandon do
name "Brandon Stark"
end
end
This may sound a bit odd. Why would you ever want to define a parent factory after a child factory? When you’ve split your factories up across different files and don’t want to prefix file names in order for the files to be loaded in a specific order.
Factory Girl looks in six places by default: factories/, factories.rb, spec/factories/, spec/factories.rb, test/factories/, and test/factories.rb. If you’ve split up your factories within one of the directories, that’s wonderful; however, what if you want a “base” factory file and multiple factory files for the various children? Before 2.2, you’d have to name that base file in order to force it to be loaded before children, and that’s just gross.
Transient attributes are a relatively new feature allowing attributes to be
passed via a hash when creating/building that don’t actually interact with the
instance (or hash, when using attributes_for). The old syntax required
calling #ignore on individual attribute declarations, which didn’t really
look great. Now, you define all your ignored attributes in a block.
FactoryGirl.define do
factory :user do
ignore do
rockstar true
four { 2 + 2 }
end
name { "John Doe#{" - Rockstar" if rockstar}" }
end
end
The old ignore syntax has been deprecated and will be removed in 3.0.
Have you ever done this?
let(:old_published_post) do
create(:post, created_at: 2.years.ago).tap do |post|
post.publish!
end
end
Now, you can just pass a block to the syntax methods (create, build,
build_stubbed, and attributes_for) and they’ll yield themselves; no more
tap! It’s a simple enough change, but four characters fewer is four
characters fewer.
Aside from getting in solid pull requests, we’re going to be reworking the test suite so it’s much more flexible and continue reworking the inner workings of the gem. There’s currently a handful of code surrounding how to evaluate attributes and the order in which we should do so that we’d like to remove completely and have attributes resolve lazily.
We’re also considering a new syntax method, decorate, which would allow for:
FactoryGirl.define do
factory :post do
title "My great post!"
body "An awesome story"
decorate :old do
title "How is this even around still?"
created_at { 2.years.ago }
end
decorate :archived do
title "This shouldn't be on the homepage"
archived true
decorate :unpublished do
published false
end
end
end
end
This would give us four factories: post, old_post, archived_post, and
archived_unpublished_post.
What features would you like to see in Factory Girl? Post them on the GitHub issues page and let us know in the comments!
I’ve noticed a handful of what I’d consider Factory Girl anti-patterns, either through pull requests, GitHub issues, or questions spread across Stack Overflow, Google Groups, etc. The more common they are, the more I realize that it will benefit plenty of other developers down the road if a list of anti-patterns can be identified and what to do instead.
The one I’d like to outline today is based off of a request to make Factory Girl sequences reset after every test run. The idea of resetting sequences after every run is that then you can start to program against these names in step definitions. For example, imagine the factory:
FactoryGirl.define do
factory :category do
sequence(:name) {|n| "Category #{n}" }
end
# ... more factories
end
And the scenario:
Scenario: Create a post under a category
Given a category exists
And I am signed in as an admin
When I go to create a new post
And I select "Category 1" from "Categories"
And I press "Create"
And I go to view all posts
Then I should see a post with the category "Category 1"
That first step is given to us by Factory Girl (check out the getting started docs in Factory Girl to find out more) and will create a category without overriding any of the defaults provided by that factory.
If all sequences were reset after every run, it’d be safe to assume that the category created as the first step of the scenario will create a category named “Category 1”.
There are two problems with this approach. Firstly, we’re now tightly coupling the scenarios to naming conventions outlined in the factories. If the sequence logic changes, all the scenarios that rely on that naming convention will break. Not fun. Secondly, it’s not at all obvious where that “Category 1” record was created. If I’m a developer who’s looking at this code with a fresh set of eyes, I’d assume that this category was created from a global setup or seed file instead of being assigned in the first step of the scenario.
The solution is to name the category explicitly.
Scenario: Create a post under a category
Given a category exists with a name of "Category 1"
And I am signed in as an admin
When I go to create a new post
And I select "Category 1" from "Categories"
And I press "Create"
And I go to view all posts
Then I should see a post with the category "Category 1"
The first step is, again, given by Factory Girl and allows us to set the name attribute. It’s now immediately obvious that the category we’re creating is the one we’re using throughout the scenario, we’re not reliant on a naming convention outlined by Factory Girl, and any other developer looking at the code won’t question where this category was created.
Factory Girl’s sequences are a great way to ensure that models with uniqueness don’t run into any naming collisions, and it’s handy to have recognizable data (instead of having every bit of data in tests runs be virtually the same). That said, when writing tests against data where the values matter, don’t leave those values to chance; assign them outright. Give them weight. The intent is much more obvious, less error prone, and requires no patches to Factory Girl.
It was a refactoring week on kumade, the Heroku deployer. Gabe Berke-Williams and Josh Clayton refactored the Packager class, splitting it out into handlers for Jammit, Less, and no-op packagers (d854184). Then Gabe Berke-Williams (gabebw) went off on his own to introduce a CommandLine class, wrapping Cocaine with some Thor (0c3840d and 72424c0). Meanwhile Joshua Clayton (joshuaclayton) changed should_not have_received to should have_received.never (6652197).
Our app making gem, suspenders, saw some updates from Matt Jankowski (mjankowski): Rails 3.1 (10ac09a, 97881fc, 354321d, c8f8c85, and db08366), bourbon (d490b36), flutie (e0bab83), the Hoptoad → Airbrake name change (6def3d7), and tying together the asset pipeline (a4ce3e7 and c760a33).
We have a one-off announcement plugin for Rails, named paul_revere. Nick Quaranto (qrush) released version 0.2.1 (3574b40 and dfbf940).
Our collection of RSpec matchers, shoulda-matchers, is not passing on my laptop. Prem Sichanugrist (sikachu) built out the continuous integration tests more to attempt to clarify the issue (3d94390, 4450c86, 1a3aeec, 6785f59, f1f5e6d).
The SCSS gem, bourbon, saw actual words being used by Matt Jankowski (mjankowski) in the documentation (b8afdea), plus Phil LaPier (plapier) updated the docs with resources for investigating browser compatibility (35539c9). He also added this sweet variable, $all-text-inputs, representing all HTML5 textual inputs like color, date, phone, password, URL, and so on (d1def76 and fb299e6). To wrap it all up he released version 0.1.8 (f3046bd).
Lots of bug fixes in paperclip, the Rails image uploader plugin gem, this week. Cody Caughlan (ruckus) added S3 support for an HTTP proxy (4661cef). Denis Yagofarov (denyago) preserved the path set for the Cocaine gem (3a35ba9). Aditya Sanghi (asanghi) gave us a warning when two models save files to the same place on the filesystem (8d43e19). Daniel Evans (danielevans) removes the temporary file after it has been uploaded and processed (748332e). Edison (edison) added a feature where you can set the file system path based on a method in the model (996ca87 and 1738f3c).
Prem Sichanugrist (sikachu) then attacked some outstanding bugs: escape the URL (23cb822), handle a space leak where an array was growing by one for each request (d18d814), make the Interpolations.hash class method confirm to the expected signature for hash methods (e526c86), and preserve the filename for S3 attachments when we know the filename (522a53e).
Prem then did some refactoring, splitting the storage tests into individual unit tests (0ca98d1, d204c7d, 31d74d6), stubbing Cocaine (0a77f64), removing some noisy debugging (fd891e0), and cleaning up whitespace (825e1f1 and b2cac53). After all of this he released version 2.4.2 (9edeb01).
Features and refactorings were the name of the game for factory_girl, the fixture replacement for Rails. Joe Ferris and Josh Clayton fixed traits so now you can override attributes of them. They did this by refactoring attributes, introducing an intermediate Declaration object that knows how to compile down to a full factory (a154e64 and f8638b). Joe Ferris (jferris) made callbacks into first-class objects with validations (0f87ca3, ede051f, and 4d30663). Thomas Walpole (twalpole) fixed an inconsistency, ensuring that parent callbacks are called before child callbacks (27c4b21).
Oh man check this: Joel Meador (janxious) updated the ChangeLog (306e51b and 88cf88)! Joshua Clayton (joshuaclayton) went along with this, too (9c95a2) before releasing version 2.1.2 (58e75bc and c1360e).
The capybara-webkit test driver, which Joe presented about at Boston Ruby, continues to improve. Gabe Berke-Williams (gabebw) gave us a documentation edit (f493b22), while Matthew Mongeau (halogenandtoast) striped and normalized spaces for consistency with Selenium (6d92f35) and also added support for unknown content types (ff0a6e7, 9257fe3, and 353fe86)
Factory Girl has seen a handful of changes over the past six weeks since we released 2.0.0. Some of the highlights:
factory :user do
name "John doe"
end
factory :post do
author :factory => :user
end
trait :male do
gender "Male"
end
trait :admin do
admin true
end
factory :user do
factory :male_user, :traits => [:male]
factory :admin, :traits => [:admin]
factory :male_admin_user, :traits => [:male, :admin]
end
factory :user do
rockstar(true).ignore
name { "Johnny#{" Rockstar" if rockstar}" }
end
> FactoryGirl.create(:user).name # "Johnny Rockstar"
> FactoryGirl.create(:user, :rockstar => false).name # "Johnny"
factory :profile do
sequence(:username) {|n| "user-#{n}" }
end
factory :user do
profile :method => :build
end
FactoryGirl.define do
factory :user do
name "John Doe"
sequence(:email) {|n| "user-#{n}@example.com" }
end
end
FactoryGirl.modify do
factory :user do
email { "#{name.downcase.underscore}@example.com" }
end
end
FactoryGirl.reload # reloads all factories, sequences, and traits
Apart from these features, we’ve ensured that Factory Girl processes different attributes (static attributes, dynamic attributes) in a reasonable order, verified it works on Rails 3.1.0, upgraded the test suite to use Mocha + Bourne instead of RR (we at Thoughtbot love Mocha), and a handful of other handy bug fixes.
Grab Factory Girl 2.1.0 and make testing easier!
I can’t count the number of times I’ve worked on Rails apps where I’m using Factory Girl and want to declare a factory with multiple parents. We’ve all been there, don’t lie.
FactoryGirl.define do
factory :user do
name "Friendly User"
factory :admin do
admin true
end
factory :male_user do
gender "Male"
name "John Doe"
factory :male_admin_user do
admin true
end
factory :teenage_male_user do
date_of_birth { 15.years.ago }
factory :teenage_admin_male_user do
admin true
end
end
end
factory :female_user do
gender "Female"
name "Jane Doe"
factory :female_admin_user do
admin true
end
factory :teenage_female_user do
date_of_birth { 15.years.ago }
factory :teenage_admin_female_user do
admin true
end
end
end
end
end
Oh, the duplication! This isn’t DRY.
Factory Girl 2.0.4 introduces traits. I’ll cut to the chase:
FactoryGirl.define do
factory :user do
name "Friendly User"
trait :admin do
admin true
end
trait :male do
gender "Male"
name "John Doe"
end
trait :female do
gender "Female"
name "Jane Doe"
end
trait :teenager do
date_of_birth { 15.years.ago }
end
factory :male_user, :traits => [:male]
factory :female_user, :traits => [:female]
factory :teenage_male_user, :traits => [:teenager, :male]
factory :teenage_female_user, :traits => [:teenager, :female]
factory :male_admin_user, :traits => [:admin, :male]
factory :female_admin_user, :traits => [:admin, :female]
factory :teenage_admin_male_user, :traits => [:teenager, :admin, :male]
factory :teenage_admin_female_user, :traits => [:teenager, :admin, :female]
end
end
Mix and match however you please. Bonus: you can assign traits like plain-old attributes.
FactoryGirl.define do
factory :user do
name "Friendly User"
trait :admin do
admin true
end
trait :male do
gender "Male"
name "John Doe"
end
factory :male_admin_user do
male
admin
end
end
end
Many props go out to Thomas Walpole, who helped get a lot of this written; thanks Thomas!
So go ahead, get your trait on; you’ll thank me later.