Welcome to Giant Robots Smashing Into Other Giant Robots — a weblog about development, business, design and technology — written by thoughtbot.
Waiting For a Factory Girl
I Can’t Get No Satisfaction (From Fixtures)
Here at thoughtbot, we’ve had it with fixtures. Is Susie an admin? Which user owns the “Exciting Test” post? Are there any categories without posts, or should I add that fixture for this test? How did this post end up in the future? Do you like asking these questions when writing tests? I don’t.
I also don’t like tests that don’t tell you anything about the context you’re testing:1 2 3 |
should "find recently updated posts" do assert_equal posts(:lions_attack), Post.most_recent end |
One method in one model being tested, and three files to look through to understand it. I’ll pass, thank you.
I’m Moving On (To Factories)
After being introduced to factories by various blogs and coworkers, I looked for a plugin to get me started. I tried out object daddy and a couple others, but none of them quite scratched that itch I needed to reach. Some had questionable implementations, some had poor (or no) tests themselves, and none of them supported everything we wanted: a nice definition syntax, support for multiple build strategies (saved instances, unsaved instances, attribute hashes, and potentially mock objects), and support for multiple factories for the same class (user, admin_user, and so on).
Eventually, I ended up just writing little methods that I included in Test::Unit::TestCase:1 2 3 4 5 6 7 8 |
def create_post (attribs = {}) attribs = { :title => 'goodbye, fixtures', :approved => true }.update(attribs) attribs[:author] ||= create_user Post.create!(attribs) end |
It got the job done, and I was finally free of fixtures, but my factory definitions were hard to follow, and became repetitive pretty fast. After discussing the pros and cons of the various implementations we’d tried, several thoughtbotters and I wrote out our ideal syntax for defining and using factories, and this weekend that theoretical syntax became a reality.
She’s a Rainbow
Introducing factory_girl: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 31 32 33 34 35 36 37 38 39 40 41 42 |
# test/test_helper.rb require 'factory_girl' # Let's define a sequence that factories can use. This sequence defines a # unique e-mail address. The first address will be "somebody1@example.com", # and the second will be "somebody2@example.com." Factory.sequence :email do |n| "somebody#{n}@example.com" end # Let's define a factory for the User model. The class name is guessed from the # factory name. Factory.define :user do |f| # These properties are set statically, and are evaluated when the factory is # defined. f.first_name 'John' f.last_name 'Doe' f.admin false # This property is set "lazily." The block will be called whenever an # instance is generated, and the return value of the block is used as the # value for the attribute. f.email { Factory.next(:email) } end Factory.define :post do |f| f.title 'undef toggle!' f.approved true # Lazy attribute blocks are passed a proxy object that can be used to # generate associations lazily. The object generated will depend on which # build strategy you're using. For example, if you generate an unsaved post, # this will generate an unsaved user as well. f.author {|a| a.association(:user) } end # Let's define a factory with a custom classname: Factory.define :admin_user, :class => User do |f| f.first_name 'Billy' f.last_name 'Idol' f.email { Factory.next(:email) } f.admin true end |
These factories can be used like so:
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 |
# test/post_test.rb class PostTest < Test::Unit::TestCase should "only find approved posts" do # Generate and save some Post instances Factory(:post, :approved => false) Factory(:post, :approved => true) posts = Post.approved assert posts.all? {|p| p.approved? } end context "a post without a title" do setup do # Build a post object @post = Factory.build(:post, :title => '') end should "not be valid" do assert !@post.valid? end end end |
Combined with Shoulda’s contexts, factory_girl makes tests readable, DRY, and explicit. Until I find another itch to scratch, I’m in testing heaven.
You Better Move On
Want to try it out for yourself? factory_girl is available on github. You can also install it using RubyGems:$ sudo gem install thoughtbot-factory_girl --source=http://gems.github.com
Also, make sure to check out the rdoc.
Update: Do you have questions or comments on factory_girl? Feel free to post them on the new mailing list.
Happy testing!
About this entry
You're reading an entry on GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS, the company weblog of thoughtbot, inc.
- Author:
- Joe Ferris
- Published:
- June 6th 12:34 PM
- Updated:
- June 12th 01:15 PM
- Sections:
- Development
thoughtbot is hiring
We are hiring web developers and web designers in both Boston and New York, NY.
What are we up to?
We built Shoulda, an eclectic set of additions to Test::Unit; Paperclip to manage uploaded files without hassle; factory_girl a replacement for Rails fixtures; Jester, a REST/ActiveResource client written in Javascript, and Squirrel, an enhancement for ActiveRecord's find syntax; — amongst some other projects.

Chad (President) and Jon (CTO) co-authored a technical book titled Pro Active Record: Databases with Ruby and Rails, which explores the ins and outs of the ActiveRecord ruby library. You can buy it today at Amazon.com.
About thoughtbot, inc.
We are a small web application development consulting business, with offices in Boston, MA and New York, NY. If you're looking to find a team for your next web development project or your new web application — get in touch.
18 comments
Jump to comment form