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

Josh Clayton

FactoryBot 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_bot_rails', '~> 3.2.0'

So, what does FactoryBot 3.2 give you?

Implicitly Call FactoryBot 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 { FactoryBot.generate(:long_string) }
end

Why type out FactoryBot. in the block? In FactoryBot 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 FactoryBot, you can still use the explicit FactoryBot.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 FactoryBot 2 syntax. Support of the different syntaxes will be removed in FactoryBot 4.0.

Why are we doing this?

We’re confident that the FactoryBot 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 FactoryBot.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 FactoryBot’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 = FactoryBot.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

FactoryBot.register_strategy(:json, JsonStrategy)

Once registered, you can refer to it like so:

FactoryBot.json(:user)

I recommend digging through FactoryBot’s current implementations of build, create, build_stubbed, and attributes_for, which use FactoryBot’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_bot.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 FactoryBot’s acceptance tests to see a couple of straightforward uses.

What’s next

As we continue to move forward with FactoryBot, 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).


Disclaimer:

Looking for FactoryGirl? The library was renamed in 2017. Project name history can be found here.