factory_girl, RSpec 2 and Rails 3

Update (7/29/2012)

Rather than using the config/application.rb below (which isn’t valid in 3.2.6 and maybe earlier) you should instead use the factory_girl_rails gem.

I’ve done my fair share of TDD and unit tests with the caveat that those activities were written using C# on the .NET platform.  A lot of static typing, interfaces, and dependency injection.  There is the golden rule you must never, ever for any reason violate and that is of course trampling your separation of concerns while writing tests.  I was appalled to say the least when I started working with Rails that this golden rule is broken regularly.  So regularly in fact that it seems almost difficult to not break it.  And why not?  For a lot of these web page/form/database applications the database is really part of the full stack and using SQLite isn’t such a test performance killer.  (You should be testing against whatever your production database of choice happens to be, but that is another discussion entirely.  Suffice it to say you can do this while developing a Rails application with little penalty.)

So let’s cave into peer pressure, step all over the separation of concerns rule for a little bit, and get a grasp on how you might approach this problem in a way that may make you feel not so dirty.

In a C# unit test I usually tend to instantiate an instance of a domain model class and populate it with values necessary to satisfy the test. Then I dependency inject that in and I’m ready to go. A lot of ceremony to say the least. We have a number of options available to us in Rails to provide us with test data. Let’s take a look:

seeds.rb

./db/seeds.rb

… is typically where you might store nonvolatile system data, like a list of states.  Test data does not belong here.

fixtures

./test/fixtures
./spec/fixtures

… is a somewhat convenient way of achieving what we want to do but using this approach will create a lot of Internet grumbling.  You can even use ERb templating to provide dynamic values for some of your attributes.  I’m not going to into anymore detail because, well, Internet grumbling.

factory_girl

… is according to its Github project page is a “[f]ixture replacement for focused and readable tests.”  If you are new to Rails, like me, it might sound a little confusing.  ”Fixture replacement?  Why can’t I just use the defaults and just get to coding?”  Certainly you could do that, but then if you were, you wouldn’t be reading this.  Also we already covered fixtures, above.  We don’t use those, it makes the Internet grumble, remember?

Here’s a trivial example based on a subset of some prototyping we were doing at work.  Maybe you don’t need to TDD your scopes, but maybe you are new to Rails and Ruby and you’re thinking “scopes?” or “I am told all of this works but… well let me see for myself.”

Create a new Rails 3 application named “fgdemo”:

# console
$ cd ~/Projects
$ rails new fgdemo

Don’t worry – its okay to have tons of Rails apps all up in your ~.

# console
$ cd fgdemo
$ mate .

… or vim or whatever you use to scratch text files into your hard disk.

Open your Gemfile – this is a file Rails creates for you and its part of bundler and add these lines:

# ./Gemfile
⋮
group :development, :test do
  gem 'rspec-rails'
  gem 'factory_girl_rails'
  gem 'rspec_multi_matchers'
end
group :development do
  # required to generate factory_girl fixtures under rails 3 as of 20110518
  gem 'rails3-generators'
end
⋮

The nice thing about Bundler is that it will install prerequisites for you. Ergo `rspec-rails` will install `rspec`.

We’ve got a couple groups defined here; these groups correspond to your RAILS_ENV environment variable. This is important during deployment on production, during testing where ever, and of course during development. We’re working in the default environment which is (you guessed it) development so we don’t need to do anything like explicitly pass a `RAILS_ENV=development` on the command line. And when we move onto executing our specs as part of our testing procedure you won’t have to RAILS_ENV=test either. (But maybe you are wondering what passing RAILS_ENV on the command line looks like? Guess what: `rake db:migrate RAILS_ENV=test`.)

`rspec_multi_matchers` isn’t strictly necessary but I find it lets me write more expressive `should` statements in my specs when dealing with enumerables. `rails3-generators` will hook into your Rails 3 generators to create factory_girl factories for each model class you create… but not just yet. This is more a convenience than anything else.

Now we execute Bundler which reads our Gemfile and installs the necessary gems onto our system:

# console
$ bundle

And let’s install RSpec into our project – this just creates some files and modifies our application configuration.

# console
$ rails g rspec:install

(`g` is shorthand for `generate` – you may use either or.)

Edit your Application’s configuration:

# ./config/application.rb
⋮
# these lines go within the Application class definition
config.generators do |g|
  g.test_framework :rspec, :fixture => true, :views => false, :fixture_replacement => :factory_girl, :view_specs => false
  g.fixture_replacement :factory_girl, :dir => 'spec/factories'
end
⋮

(Without the :dir hash key above rails3-generators will infuriatingly create factories at test/factories.)
Then let’s make some changes to our `spec_helper.rb` file. This file should be required at the top of every `_spec.rb` file you generate or author but there’s no magic that autoloads it behind the scenes. (The Rails generator will hook into the rspec-rails stuff and actually add a `require ’spec_helper’` line at the top of each `_spec.rb` file it generates for you.)

# ./spec/spec_helper.rb
⋮
require 'factory_girl'
# then look for a line like the one below and comment it out
#config.fixture_path = "#{::Rails.root}/spec/fixtures"
⋮

Now we will make a model!

# console
rails g model AdTargeting ad_id:integer start_date:datetime end_date:datetime

A column named `id` will be generated automatically for you (among other things). `ad_id` is a key value referring out to another domain class in our prototype and the date fields are what we’ll be scoping here as part of our spec/factory_girl exercise. The end result of this command will be a number of things that I’m not going to go into detail here.

Next let’s update our development database schema:

# console
$ rake db:migrate

Since we’re practicing good TDD let’s go write a spec:

# ./spec/models/ad_targeting_spec.rb
require 'spec_helper'

describe AdTargeting do
  describe "date_between scope" do
    it "returns only the :valid_ad_with_date_range_for_today" do
      Factory.create(:invalid_ad_for_today)
      Factory.create(:valid_ad_with_date_range_for_today)
      AdTargeting.date_between(DateTime.current).should have(1).things
    end
  end
end

That may not make a whole lot of sense to you if you have no RSpec background but you should be able to get the gist if you’re coming to Rails from another framework. So when we run this at the console:

# console
$ rake spec

You’re going to be red because we haven’t yet updated the `.\spec\factories\ad_targeting.rb` file. Let’s do that now:

# ./spec/factories/ad_targeting.rb
Factory.define :empty_ad, :class => AdTargeting do |a|
  a.ad_id        0
  a.start_date   DateTime.current.beginning_of_day
  a.end_date     DateTime.current.end_of_day
  # other attribute defaults ...
end

Factory.define :invalid_ad_for_today, :parent => :empty_ad do |a|
  a.ad_id        2
  a.start_date   DateTime.current.beginning_of_day - 2
  a.end_date     DateTime.current.end_of_day - 2
end

Factory.define :valid_ad_with_date_range_for_today, :parent => :empty_ad do |a|
  a.ad_id        3
  a.start_date   DateTime.current.beginning_of_day - 2
  a.end_date     DateTime.current.end_of_day + 2
end

So the code above is a bit more lengthy than it absolutely has to be, but my goal here is to help you feel not-so-dirty for violating separation of concerns. And how do we do that? Misdirection! See, our factory definitions are inheriting from a factory definition!

Let’s run our specs again:

# console
$ rake spec

Red again as we have not yet defined our scope.

# ./app/models/ad_targeting.rb
class AdTargeting < ActiveRecord::Base
  scope :date_between, lambda { |date| where('start_date <= ? and end_date >= ?', date, date) }
end

While still trivial I wanted to get you thinking about what you can do with scopes beyond just the regular “field = value” approach. (Before you start trying to use scopes to OR conditions together its better to know that as of Rails 3 you cannot OR. You’ll need to look at squeel in order to do that.)

And run the specs yet again…

# console
$ rake spec

Green! Awesome. So… what is this factory_girl doing behind the scenes? Are we really violating separation of concerns? Is the full stack being exercised? Let’s let the log file speak for itself. Open a new terminal and…

# console
$ cd ~/Projects/fgdemo
$ tail -f ./log/test.log

Then run those specs again (in your original terminal)…

# console
$ rake spec

Watching the tail of the log file you will see a test database is created and your factory_girl definitions will be inserted into the appropriate tables when `Factory.create(:symbol)` is invoked. Yes the appropriate tables will be cleaned up at the end of each test so you don’t need to worry about the order in which your tests execute.