GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS

Written by thoughtbot

FactoryGirl 3.2: so awesome it needs to be released during RailsConf

FactoryGirl 3.2 brings a slew of new features; while I'd considered breaking it all up into 3.2 and 3.3 releases, I decided to bundle them for MORE AWESOMENESS during RailsConf.

To install, add (or change) your Gemfile:

gem 'factory_girl_rails', '~> 3.2.0'

So, what does FactoryGirl 3.2 give you?

Implicitly Call FactoryGirl from Dynamic Attributes

Dynamic attributes are attributes you declare on a factory that are defined with a block to be evaluated every time the factory is run.

factory :user do
  name { NameGenerator.generate }
end

There have been a handful of times where I want to share a sequence or create a record inside of an attribute, but who wants to do this?

sequence(:long_string) {|n| "#{LoremIpsum.generate}#{n}" }

factory :user do
  name { FactoryGirl.generate(:long_string) }
end

Why type out FactoryGirl. in the block? In FactoryGirl 3.2, you don't have to:

sequence(:long_string) {|n| "#{LoremIpsum.generate}#{n}" }

factory :user do
  name { generate(:long_string) }
end

If it just so happens that if you have a method named generate on your object and it's colliding with FactoryGirl, you can still use the explicit FactoryGirl.generate(:long_string).

Deprecate Alternate Syntaxes

We've deprecated alternate syntaxes. This means if you're using the object_daddy or machinist syntax, you'll be urged to upgrade to the FactoryGirl 2 syntax. Support of the different syntaxes will be removed in FactoryGirl 4.0.

Why are we doing this?

We're confident that the FactoryGirl 2 syntax is better than any other syntax out there. It's clear, concise, and is incredibly flexible. It allows for usage of traits and other benefits we've added to the default syntax.

Skip the to_create Block Easily

Certain factories may skip the to_create block altogether (which normally calls #save!). Instead of calling to_create { } on the factory, you can now call skip_create.

factory :user do
  skip_create
end

Slim Down initialize_with

If you use initialize_with, you'll know you can define your own construction method instead of just calling new without arguments. This becomes tedious, however, when you constantly repeate the build class.

We've created a shorthand for calling new within initialize_with:

factory :user do
  name "John Doe"

  initialize_with { new(name) }
end

Other class methods won't be supported, but the common case (new) is covered for you!

Register Custom Build Strategies

Ever wish you could call FactoryGirl.json(:user) to get a JSON representation of your factory? Now you can. Ever wish attributes_for would build your association data instead of ignoring associations altogether? You can overwrite FactoryGirl's implementation of attributes_for with your own code.

Here's an example of registering your own JSON strategy, decorating the currently registered create strategy with additional behavior.

class JsonStrategy
  def initialize
    @strategy = FactoryGirl.strategy_by_name(:create).new
  end

  delegate :association, to: :@strategy

  def result(evaluation)
    @strategy.result(evaluation).to_json
  end
end

To register the new strategy, run

FactoryGirl.register_strategy(:json, JsonStrategy)

Once registered, you can refer to it like so:

FactoryGirl.json(:user)

I recommend digging through FactoryGirl's current implementations of build, create, build_stubbed, and attributes_for, which use FactoryGirl's brand new register_strategy interface.

Add ActiveSupport::Notifications Instrumentation

Now that we've dropped Rails 2 support, we can start using some of the awesome features of Rails 3. This includes ActiveSupport::Notifications, which uses pub/sub and allows us to track when factories get run.

Want to see which factories take longer than half a second to run? If you're using RSpec, you could add this to the before(:suite) block:

ActiveSupport::Notifications.subscribe("factory_girl.run_factory") do |name, start, finish, id, payload|
  execution_time_in_seconds = finish - start

  if execution_time_in_seconds >= 0.5
    $stderr.puts "Slow factory: #{payload[:name]} using strategy #{payload[:strategy]}"
  end
end

Any slow factories will be written out to STDERR.

The other use case that I've wanted for a long time is keeping track of what factories I'm creating and the strategies used. Creating a record is obviously going to be significantly slower than building a stubbed version of it, so printing out a table of all the factories (and the number of times each strategy is executed for that factory) should be fairly straightforward. Feel free to dig into FactoryGirl's acceptance tests to see a couple of straightforward uses.

What's Next?

As we continue to move forward with FactoryGirl, we're planning on making it easier to use initialize_with. I'd also love to see a pull request or example of generating a table of factories and strategy count (much like rake stats).