Refactoring Tests to Include Factories with FactoryGirl
Now that we have our FactoryGirl configuration in place, let's start to refactor our tests to use factories instead of manually creating test specs.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

Now that we have our FactoryGirl configuration in place, let's start to refactor our tests to use factories instead of manually creating test specs.

To start I'm going to delete the controllers subfolder in spec since we won't be using it for this application. This is mainly because integration testing is comprehensive and provides test coverage for the core features we want to build. So, we don't have to test controllers separately. Next, let's look at the post_spec.rb file in spec/features. In this file, we can simply use:

# spec/features/post_spec.rb

describe 'navigate' do
  before do
    @user = FactoryGirl.create(:user)
    login_as(@user, :scope => :user)
  end

  # rest of the code hidden for brevity

This will create a user in place of the long code we had earlier. Here, FactoryGirl will create a new instance of user in the test database.

Let's run rspec to see if this refactoring is working, and it does!

Likewise, let's use FactoryGirl to create our two posts as well. Also, in order for our content matcher to keep working, we will have to give the exact content for each post too, like this:

# spec/features/post_spec.rb

it 'has a list of posts' do
  post1 = FactoryGirl.create(:post)
  post2 = FactoryGirl.create(:second_post)
  visit posts_path
  expect(page).to have_content(/Rationale|content/)
end

If you run rspec now, we have an error. It says that the method full_name is undefined. If you look at our factories in posts.rb, you'll see that our posts are not referencing users, so we'll have to add that. Next, we'll have to change our users.rb file too. We'll have to give a different email to every user, to avoid validation errors. In general, devise will not allow the application to create users with the same email address, and this is why we have to make this change.

Let's make the following changes to the factories/users.rb file:

# spec/factories/users.rb

FactoryGirl.define do
  factory :user do
    first_name 'Jon'
    last_name 'Snow'
    email "test@test.com"
    password "asdfasdf"
    password_confirmation "asdfasdf"
  end

  factory :admin_user, class: "AdminUser" do
    first_name 'Admin'
    last_name 'User'
    email "admin@user.com"
    password "asdfasdf"
    password_confirmation "asdfasdf"
  end
end

After making these changes, make sure to update the posts.rb file too, like this:

# spec/factories/posts.rb

FactoryGirl.define do
  factory :post do
    date Date.today
    rationale "Some Rationale"
    user
  end

  factory :second_post, class: "Post" do
    date Date.yesterday
    rationale "Some more content"
    user
  end
end

If you rspec again, you'll get an error saying that the email has already been taken. So, this quick fix is not working and we have to do it the hard way. What's happening here is every time when the application tries to create a user, it encounters the same email address and devise doesn't allow it to create a new user with the same email address. To fix this problem, let's use an incriminator that will increment the email address by 1, so it is unique each time.

# spec/factories/users.rb

FactoryGirl.define do
  sequence :email do |n|
    "test#{n}@example.com"
  end

  factory :user do
    first_name 'Jon'
    last_name 'Snow'
    email { generate :email }
    password "asdfasdf"
    password_confirmation "asdfasdf"
  end

  factory :admin_user, class: "AdminUser" do
    first_name 'Admin'
    last_name 'User'
    email { generate :email }
    password "asdfasdf"
    password_confirmation "asdfasdf"
  end
end

In this code, we have a block right at the top that will add 1 each time to create a unique email address. Every time, when a new email has to be generated, FactoryGirl will know to go to the sequence block to generate a new email. This way, every email will be unique.

Now, let's run rspec and everything is fine now.

Let's go back to refactoring our post_spec.rb file. I'm going to use a method called build_stubbed instead of create for creating our posts. When we use create it slows down our test process since it forces the application to touch the database, so we are using build_stubbed method when we just need to stub our data out. Essentially, this method does not hit the database, but it mimics the process, so your test performance will be much faster.

# spec/features/post_spec.rb

it 'has a list of posts' do
  post1 = FactoryGirl.build_stubbed(:post)
  post2 = FactoryGirl.build_stubbed(:second_post)
  visit posts_path
  expect(page).to have_content(/Rationale|content/)
end

Next, let's go to post_spec.rb file in the folder spec/models. Like in the previous files, let's use FactoryGirl to create an instance of post. However this time we'll need to use the create method since we're testing how the system works with the database itself.

# spec/models/post_spec.rb

RSpec.describe Post, type: :model do
  describe "Creation" do
    before do
      @post = FactoryGirl.create(:post)
    end

Run rspec and everything should pass.

Lastly, move to user_spec.rb and make the same change here too.

# spec/models/user_spec.rb

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

Everything should be working fine now. The code looks better, and we are not touching the database as frequently as before, which will help with our test performance.

Resources