Waiting For a FactoryGirl

Waiting For a FactoryGirl

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:

should "find recently updated posts" do
  assertequal posts(:lionsattack), 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:

def createpost (attribs = {})
  attribs = {
    :title     => 'goodbye, fixtures',
    :approved => true
  }.update(attribs)
  attribs[:author] ||= createuser
  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 factorygirl:

# test/testhelper.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.firstname 'John' f.lastname '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 :adminuser, :class => User do |f| f.firstname 'Billy' f.last_name 'Idol' f.email { Factory.next(:email) } f.admin true end

These factories can be used like so:

# 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? factorygirl is available on <a href=“http://github.com/thoughtbot/factorygirl/tree/master”>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 factorygirl? Feel free to post them on the new <a href=“http://groups.google.com/group/factorygirl”>mailing list.

Happy testing!

Joe Ferris Developer

Sharpen your programming skills by completing coding exercises that are reviewed by other developers at Upcase today.