How to Refactor Integration Tests with RSpec and Capybara
In this guide we are going to refactor our `post_spec.rb` because it's starting to get a bit unwieldy.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

In this guide we are going to refactor our post_spec.rb because it's starting to get a bit unwieldy.

Let's start off by creating some let statements. Essentially, this will give us access to users, but a new one won't be run with each block. Instead, it will run just once. This will allow us to use the local variable user throughout the program.

# spec/features/post_spec.rb

let(:user) { FactoryGirl.create(:user) }

Next, we are going to force our post to have the same user id every time. Here is how we can integrate our let statement.

# spec/features/post_spec.rb

let(:post) do
  Post.create(date: Date.today, rationale: "Rationale", user_id: user.id)
end

Now we can update our before block to only log the user in (notice how I changed the login argument to use the user variable).

# spec/features/post_spec.rb

before do
  login_as(user, :scope => :user)
end

Now, move down to the test case that says "has a scope so that only post creators can see their posts". Let's remove post1 and post2 completely. Now if you run rspec you'll see that we have a failure in the "Delete" block. We'll fix that later.

Next, go to the "edit" block and remove all the code where we create a user and log them in. We can do this because we now have access to the post variable from our let, so we don't need any of this duplicate code.

Also, let's change all of the @post variables in the edit block to post since the instance variable doesn't exist anymore. If you run rspec now, this test passes.

Now, let's fix our "delete" test case. Remove the code where you create a new post since that is no longer needed. Also, change @post to post as we did in the edit block. If you run rspec, it still fails.

This failure is due to the permission structure. If we logout our test user and then create a new user, log in that user and create a post with the new user id it will work.

If you run rspec, all of the tests should now pass.

Overall I'm happy with this refactor. It's cleaned up a number of the duplicate code items and takes advantage of FactoryGirl factories. The update post_spec file is shown below:

# spec/features/post_spec.rb

require 'rails_helper'

describe 'navigate' do
  let(:user) { FactoryGirl.create(:user) }

  let(:post) do
    Post.create(date: Date.today, rationale: "Rationale", user_id: user.id)
  end

  before do
    login_as(user, :scope => :user)
  end

  describe 'index' do
    before do
      visit posts_path
    end

    it 'can be reached successfully' do
      expect(page.status_code).to eq(200)
    end

    it 'has a title of Posts' do
      expect(page).to have_content(/Posts/)
    end

    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

    it 'has a scope so that only post creators can see their posts' do
      other_user = User.create(first_name: 'Non', last_name: 'Authorized', email: "nonauth@example.com", password: "asdfasdf", password_confirmation: "asdfasdf")
      post_from_other_user = Post.create(date: Date.today, rationale: "This post shouldn't be seen", user_id: other_user.id)

      visit posts_path

      expect(page).to_not have_content(/This post shouldn't be seen/)
    end
  end

  describe 'new' do
    it 'has a link from the homepage' do
      visit root_path

      click_link("new_post_from_nav")
      expect(page.status_code).to eq(200)
    end
  end

  describe 'delete' do
    it 'can be deleted' do
      logout(:user)

      delete_user = FactoryGirl.create(:user)
      login_as(delete_user, :scope => :user)

      post_to_delete = Post.create(date: Date.today, rationale: 'asdf', user_id: delete_user.id)

      visit posts_path

      click_link("delete_post_#{post_to_delete.id}_from_index")
      expect(page.status_code).to eq(200)
    end
  end

  describe 'creation' do
    before do
      visit new_post_path
    end

    it 'has a new form that can be reached' do
      expect(page.status_code).to eq(200)
    end

    it 'can be created from new form page' do
      fill_in 'post[date]', with: Date.today
      fill_in 'post[rationale]', with: "Some rationale"
      click_on "Save"

      expect(page).to have_content("Some rationale")
    end

    it 'will have a user associated it' do
      fill_in 'post[date]', with: Date.today
      fill_in 'post[rationale]', with: "User Association"
      click_on "Save"

      expect(User.last.posts.last.rationale).to eq("User Association")
    end
  end

  describe 'edit' do
    it 'can be edited' do
      visit edit_post_path(post)

      fill_in 'post[date]', with: Date.today
      fill_in 'post[rationale]', with: "Edited content"
      click_on "Save"

      expect(page).to have_content("Edited content")
    end

    it 'cannot be edited by a non authorized user' do
      logout(:user)
      non_authorized_user = FactoryGirl.create(:non_authorized_user)
      login_as(non_authorized_user, :scope => :user)

      visit edit_post_path(post)

      expect(current_path).to eq(root_path)
    end
  end
end

Resources