Words and Code

One writer’s journey from words to code.

Tackling Those Tests, Part 2: Getting Fun and Functional With FactoryGirl

#technicaltuesdays, rails, testing

This blog post is part of a series on testing. Read Part 1 here.

Good things always come in pairs, and that couldn’t be more true when it comes to testing. Like milk and cookies or peanut butter and jelly, test suites and test data are at their best when they’re put together. As we discovered last week, a thoughtful test suite is important when it comes to checking our assumptions. But even the most comprehensive test suite is nothing without the appropriate amount – and type – of test data to support it.

Every Rails application comes with a production, development, and test environment, and good test data is an indication of a well-constructed testing environment. But not all data is created equally. To take a cue from George Orwell, we could go so far as to claim that some forms of test data are more equal than others.

Enter FactoryGirl, a gem that I’ve recently discovered to be the most efficient way and painless way of creating test data within a Rails application. Generating test data is often the culprit for not only a great deal of pain and sufferring, but also some annoying bugs that are hard to catch. In fact, one of the most excruciating bugs I’ve ever dealt with came from a single line of code, meant to create some test data. So it seems fitting that this week we tackle the most dangerous (yet thrilling!) part of testing: generating test data.

Setting Up The Factory Floor

Since we’re implementing FactoryGirl on top of a Rails application, we’ll work exclusively with the factory_girl_rails gem, which has been built specifically for this purpose.

Once we’ve added gem 'factory_girl_rails' to our Gemfile and run a bundle install in the terminal, we’re ready to start setting up our factories. We’ll start by creating factories for our ReadingList model from last week’s post.

  • Within our /spec directory, we’ll need to create a factories directory. This is where each of the factories for each model will live.

  • Inside of our /spec/factories/ subdirectory, we’ll create a file for our ReadingList factory. The convention for naming factories is to use the plural form of the model name in snake case, with the word “factory” appended to the end. In our case, that file would be named reading_lists_factory.rb.

  • In our newly-created ReadingList Factory, we’ll add a block that defines what our FactoryGirl object will look like. Every single factory you make for any instance of test data will begin with a block exactly like this one:

1
2
FactoryGirl.define do
end
  • Next, we’ll want to give this test object a name. For this very basic test suite, we only want to test one ReadingList object, so we’ll stick with a simple name: reading_list. Keep in mind that whatever name we give this test object is what we’ll be using to refer to it inside of our tests. Inside of our initial FactoryGirl block, we’ll define this specific object like this:
1
2
3
4
FactoryGirl.define do
  factory :reading_list,  do
  end
end
  • Lastly, we need to define some attributes for our test object. For now, our ReadingList test object needs only three columns in the database: one for its id, one for its title, and a foreign key of the User it belongs to. Here’s what our final test object definition looks like:
1
2
3
4
5
6
FactoryGirl.define do
  factory :reading_list,  do
    user
    title "Books I Want To Read"
  end
end

Pretty cool, right? We don’t have to give our test object an id, because FactoryGirl will generate one when we call on it to create a new test object. So what’s that user line doing in there? Well, it’s creating an association between two factories! When FactoryGirl sees user, it looks for a file with the path /spec/factories/user_factory.rb, and creates an instance of a User test object, which it then uses to build our ReadingList object.

But right now, our code will give us a big, nasty error. Can you guess the reason behind that? Why, we don’t have a User factory, of course! I guess we better get on that.

Form Follows Function

Now that we know how to set up our factories pretty quickly, we can hop on making a users_factory. For now, our User objects have only a first_name and a last_name attribute, so we’ll create a User test object that satisfies these requirements.

But, some Users can also be admins, but this is an optional trait that not all Users will have. This is something we definitely want to test, but we also want to keep our code DRY. How do we handle this? By nesting factories!

1
2
3
4
5
6
7
8
9
10
FactoryGirl.define do
  factory :user do
    first_name "Vaidehi"
    last_name "Joshi"

    factory :admin do
      admin true
    end
  end
end

This tells FactoryGirl to define two different user instances: a user test object, and an admin test object, which inherits the traits of the user object – namely, its first_name and last_name attributes.

While we’re at it, why don’t we set up our books_factory as well? That might look something a little like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
FactoryGirl.define do
  factory :book1, class: Book do
    title "A Game Of Thrones"
    reading_list
    read true
  end

  factory :book2, class: Book do
    title "A Storm Of Swords"
    reading_list
    read false
  end
end

Wait, what’s up with the class: Book syntax? We haven’t seen that yet! Well, not to worry – you only have to use it in a specific situation! What exactly is that situation, you might ask? Well, the only time you ever need to explicitly define a FactoryGirl object’s class is if the name of the object – book1 or book2 in our case – is different from the object’s class name. If we had only a single book test object, we wouldn’t have to define the class name, since FactoryGirl will know to look for it in the books_factory.rb file.

Remember last week when we had to manually create two different Book objects every single time we wanted to create a new test object? Well, here we’re defining two test objects in a singular, isolated place.

Awesome! Now it’s time to bring it all together in our cleaned-up test suite!

Bypassing Testing Bugs

When we wrote out the first iteration of our test suite last week, we had a few different let blocks, inside of which we called create! and build on our different objects. Our let blocks definintely helped us tidy up our tests, since we built and created all of our test objects at once, at the very top of our do block.

But as is the case with most of programming, there’s a better way to do that. Now that we’ve implemented FactoryGirl, we can cut out some of those blocks, and only call them when we need them. And, we don’t need to create an instance with the attributes explicitly defined in a block – instead, we just tell FactoryGirl to create it for us!

Here’s a refactored version of last week’s tests, now with the factory_girl_rails to help us out:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
require 'rails_helper'

Describe ReadingList do
  let(:reading_list) { create(:reading_list) }

  it 'belongs to a user' do
    user = create(:user)
    expect(reading_list.user).to include(:user)
  end

  it 'has a title' do
    expect(reading_list.title).to eq("Books I Want To Read")
  end

  describe "#percentage_read" do
    it "calculates the percentage of books read in a list" do
      book1 = create(:book1)
      book2 = create(:book2)

      expect(reading_list.percentage_read).to eq(50.00)
    end
  end
end

You’ll notice that I’ve replaced build with create in this iteration of tests. I initially had used build, but when I started implementing FactoryGirl, I hit a wall. In fact, I spent an unmentionable number of hours trying to figure out what on earth I was doing wrong!

It turns out I was using the wrong tool for the job. The build method creates an instance, but does not save it. The create method, on the other hand, both creates and persists the object. This was one of the most important debugging lesson I learned while writing tests, and it was not a fun lesson to learn. I hope that at the very least, you won’t have to struggle through that bug like I did!

The thing that I valued the most during this debugging process was learning how FactoryGirl actualy works. If those build and create methods seem an awful lot like ActiveRecord to you, that’s because it IS ActiveRecord!

In fact, when we call create(:book1), the magic of FactoryGirl actually does the following:

  1. Creates a new ReadingList.
  2. Saves the ReadingList.
  3. Creates a new Book.
  4. Associates that Book with the ReadingList.
  5. Saves the Book.

Amazing, right? We get so much functionality, all in a single method call! Learning this made me appreciate what FactoryGirl does so much more than if I had just included it blindly in my Gemfile without giving it a second though.

Thoughtbot, the creators of the factory_girl gem, has a great post explaining how it interacts with ActiveRecord. I found their explaination super helpful when I was first learning about FactoryGirl:

When you invoke a factory, factory_girl uses your definitions to compile a list of attributes that should be assigned to that instance, as well as any associated factories. It saves associations first so that foreign keys will be properly set on dependent models. To create an instance, it calls new without any arguments, assigns each attribute (including associations), and then calls save!. factory_girl doesn’t do anything special to create ActiveRecord instances. It doesn’t interact with the database or extend ActiveRecord or your models in any way.

Sure, when it comes to the world of testing, the night may be dark and full of terrors. But with FactoryGirl on your side, you’ll feel safer, as though you’ve got an army of dragons to back you up. In my head, they’re super cute ones kinda like these:

Tune in again next week, when I’ll cap off this series by sharing two more gems we can add to our army of testing dragons: shoulda-matchers for writing quick and easy validations, and database_cleaner, the key to unlocking your dreams of a neat and tidy testing database. Until then, test on, my friends – test on!

tl;dr?

  • The factory_girl_rails gem is used to generate test data for a Rails application, and each factory defines the attributes and associations of a test object. All factory files should be created in the ./spec/factories subdirectory.
  • This post only covers a couple of the tricks that FactoryGirl has up her sleeve. To read them all, check out the gem’s extensive documentation.
  • This tutorial is super detailed and I referred to it frequently while writing this blog post. If you want to learn more about testing and implementing FactoryGirl, give it a read.
  • Did you know that factories and fixtures are actually quite different? No? Well then, you should read this post and get all caught up.