- 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 abelongs_to
relationship to anAuthor
and call them in specs the same way you call them in the applicationStore 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 railsSet 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