FactoryGirl Rails
A walkthrough of the popular FactoryGirl Rails gem that aids the TDD process in creating model objects.
Guide Tasks
  • Read Tutorial

What FactoryGirl Rails Provides

Implementing fixtures into a Rails test suite can be helpful since it allows you to manage the attributes for test records to be managed from one file. Imagine if you created new records manually in every one of your test files, everytime you added/removed/changed a model's attributes you would need to change the create call in EVERY file, which would be a nasty way to test an application.

That is where FactoryGirl comes to the rescue. This gem provides a clean syntax for creating new model instances and lets you manage the attributes from a single fixture file. The specific version that we'll be walking through today is integrated to work specifically with Rails.

FactoryGirl enables you to:

  • Create new model instances from a fixture file

  • Interact with the test database to create test data

  • Automatically generate fixtures when you run Rails generators

  • Allow for automatic database relationship connections, e.g. you can have a fixture for a Post that has a belongs_to relationship to an Author and call them in specs the same way you call them in the application

  • Store the created values in variables

  • Create multiple fixtures for each model

Drawbacks

Using FactoryGirl for fixtures has become the standard for testing Rails applications and I haven't come across very many drawbacks. All in all I think that Seth MacPherson said it best when he said "Tests become much easier to maintain when you can request a model instance that is always current. Using Factory Girl, a model is never bound to a particular phase of your application’s development. They are dynamically loaded from the current state of your application. Were there new Customer attributes introduced in that last merge? No problem, Factory Girl already sees them."

Implementation / FactoryGirl Rails Example

For the implementation, I'm going to continue building out the app that we started in the rspec-rails walkthrough (if you didn't go through that tutorial you can clone the repo from here).

To install the Rails version of FactoryGirl let's open up the Gemfile and add in the gem in the same block that we have the rspec-rails gem inside:

group :development, :test do
  gem 'rspec-rails', '~> 3.0'
  gem 'factory_girl_rails', '~> 4.5'
end

After running bundle open up the spec/rails_helper.rb file and add the following line inside of the configure block:

# spec/rails_helper.rb

config.include FactoryGirl::Syntax::Methods

Finishing up our setup, create a new directory called factories and place it inside of the spec/ directory:

mkdir spec/factories

Opening up the schema file you'll see that we already have a model for the base application:

# db/schema.rb
create_table "articles", force: :cascade do |t|
  t.string   "title"
  t.string   "author"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end

Let's start the FactoryGirl example by manually creating a fixture for the Article model, create a new file in the factories directory spec/factories/articles.rb and add in the following code:

FactoryGirl.define do
  factory :article do
    title "My Amazing Article"
    author "Jon Snow"
  end
end

Now let's see if this is working, we can initially test it out in the rails console, running it in the test environment:

rails c -e test

Now run the command:

FactoryGirl.create(:article)

And you'll see the following output:

(0.1ms)  begin transaction

  SQL (0.4ms)  INSERT INTO "articles" ("title", "author", "published_status", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["title", "My Amazing Article"], ["author", "Jon Snow"], ["created_at", "2016-01-21 21:48:18.518654"], ["updated_at", "2016-01-21 21:48:18.518654"]]

(3.3ms)  commit transaction

 => #<Article id: 1, title: "My Amazing Article", author: "Jon Snow", created_at: "2016-01-21 21:48:18", updated_at: "2016-01-21 21:48:18">

Awesome! With one line we can dynamically create a test data fixture that we can use in our specs.

Now that we know that our FactoryGirl implementation is working, we can refactor our model test suite like below:

# spec/models/article_spec.rb

require 'rails_helper'

RSpec.describe Article, type: :model do
  before do
    @article = FactoryGirl.create(:article)
  end

  describe "creation" do
    it "can be created if valid" do
      expect(@article).to be_valid
    end

    it "will not be created if not valid" do
      @article.title = nil
      expect(@article).to_not be_valid
    end
  end
end

Notice how I was able to remove the creation calls from each of the specs and place a single call to the FactoryGirl create method, passing it in the :article symbol. To see how much more efficient this can make your test maintenance, let's add a new attribute to the Article model:

rails g migration add_published_status_to_articles published_status:string

After running rake db:migrate we can open up the factories/articles.rb file and simply add in the new attribute:

FactoryGirl.define do
  factory :article do
    title "My Amazing Article"
    author "Jon Snow"
    published_status "published"
  end
end

Now our specs that call on the FactoryGirl created object will automatically have access to the new test data. This is much more efficient than adding those values in manually.

So how does FactoryGirl work with relational setups? Let's create a new model to see:

rails g model ArticleTag tag_name:string article:references

You'll see that this model generator now automatically creates a factory for us: spec/factories/article_tags.rb. Opening up the file you can see it has the following contents

FactoryGirl.define do
  factory :article_tag do
    tag_name "MyString"
    article nil
  end
end

Let's make a small change and remove the nil value next to the article attribute, so the file should now look like this:

FactoryGirl.define do
  factory :article_tag do
    tag_name "MyString"
    article
  end
end

Since we don't get into bad habits, make sure to add in the has_many declaration in the models/article.rb file:

class Article < ActiveRecord::Base
  validates_presence_of :title, :author
  has_many :article_tags
end

After running rake db:migrate let's test out our new factory, open up the rails console in test mode again and run the following command:

tag = FactoryGirl.create(:article_tag)

This will give the following output:

(0.1ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "articles" ("title", "author", "published_status", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["title", "My Amazing Article"], ["author", "Jon Snow"], ["published_status", "published"], ["created_at", "2016-01-21 22:08:30.389380"], ["updated_at", "2016-01-21 22:08:30.389380"]]
   (1.4ms)  commit transaction

(0.0ms)  begin transaction
  SQL (0.2ms)  INSERT INTO "article_tags" ("tag_name", "article_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["tag_name", "MyString"], ["article_id", 4], ["created_at", "2016-01-21 22:08:30.393239"], ["updated_at", "2016-01-21 22:08:30.393239"]]
   (1.1ms)  commit transaction

=> #<ArticleTag id: 5, tag_name: "MyString", article_id: 4, created_at: "2016-01-21 22:08:30", updated_at: "2016-01-21 22:08:30"> 

Notice something interesting with this SQL? Not only did this create a new ArticleTag, it also created a new Article, FactoryGirl does this automatically so that you can work with the data the same way your application will. You can test this out further by running the following command in the terminal:

tag.article.title

This will print out => "My Amazing Article", referencing the title value from the Article factory. Pretty cool, right?

The last thing we'll walk through is how to have multiple factories for a single model, let's update the new ArticleTag factory like below:

# spec/factories/article_tags.rb

FactoryGirl.define do
  factory :article_tag do
    tag_name "MyString"
  article
  end

  factory :second_article_tag, class: "ArticleTag" do
    tag_name "My Second Tag"
    article
  end
end

Make sure to note that you need to pass in the class name for the factory for any additional factories that you want to create. Let's run this in the test console and create a few article tags:

FactoryGirl.create(:article_tag)

followed by:

FactoryGirl.create(:second_article_tag)

Very nice, it's all working! So you now know how to:

  • Integrate the FactoryGirl gem for rails

  • Set it up to work as the fixture mechanism for your test suite

  • Create a basic factory

  • See how factories work with generators

  • Customize a factory to work with database relations

Code

Resources