Integration Specs - How to minimize time to run specs

This came up during office hours today, and I wanted to get some insight on refactoring my specs to make them run faster. Additionally, how do I get around creating a bunch of records in the db for integration tests? It makes more sense to me that unit tests don’t require saved records, but I’m having a hard time seeing how to get around it with integration tests.

For example, here are some integration specs that test the #renew action of a listing (in an apartment search app):

  context "renew listing" do
    let(:user) { FactoryGirl.create :user }

    it "allows a user to renew a listing on the user's My Account page" do
      listing = FactoryGirl.create(:listing, user: user)
      log_in_as user
      visit renew_listing_path(listing.slug)
      page.should have_content "Your listing was renewed for another 30 days."
      page.should have_link "Renew Listing"
    end

    it "does not renew a listing when the listing already has the latest possible expiry date" do
      listing = FactoryGirl.create(:listing, user: user, end_date: Time.now.to_date+29.days)
      log_in_as user
      visit renew_listing_path(listing.slug)
      page.should have_content "You cannot renew this listing at this time"
    end
  end

@joshclayton

@realDLee in this case (and for most acceptance tests), you can’t get around hitting the database in order to assert that the application is behaving the correct way. Because there’s no before block (or, if there is, it’s not immediately obvious here), there doesn’t seem to be any additional data created, so I’d consider this set of tests done in terms of what you could probably do in these tests specifically to speed them up.

Are these tests specifically taking a long time to run? If they are, it’s likely that the culprit is either the user factory or the listing factory - with factories, I try cutting down the factories down to the least amount of data necessary to get the object valid and persist to the database, and then add additional information as necessary. This often results in less data created (because most models don’t have associations that they validate exist) and will speed up the suite.

Remember, you can call rspec --profile to find the slowest specs in your suite. I’d recommend running it, seeing how slow they are (especially in comparison to the other ones in that list) and start tackling those tests. Often times if you’re using let, let!, subject, or before, you’ll notice the slow tests come in clumps (they often do too much across a handful of specs) so that’s another thing to keep an eye out for.

1 Like

Hello @realDLee. Are you currently using any pre-loader like zeus, spring, or spork? These tools will load up your Rails environment and keep it ready so that you can avoid the 5-10+ seconds required to boot up Rails when you want to run individual specs.

As @joshclayton said, there are some unavoidable costs time-wise when it comes to integration tests as the idea is to exercise the system as a whole, but using one of the pre-loaders can help immensely in reducing the total time needed to run individual specs.

You can find more detail on how we use zeus in this blog post: Improving Rails boot time with Zeus.

1 Like

What does log_in_as_user do? Is it actually loading the login page, filling it out, and logging in? If so, I’d say that behavior can be isolated in its own test and then all the other tests that require a logged in user can stub that out.

Clearance, for instance, offers the Clearance back door in testing, which I would use like so:

visit renew_listing_path(listing.slug, as: user)

I’ve also seen significant acceptance test speedups by lowering the BCrypt cost factor in the test environment. BCrypt is intentionally “slow” for security reasons. We don’t need that in test, so we can instruct it to use a lower cost. Devise and recent versions of clearance will do this for you. If you’ve rolled your own authentication, however, you’re also on your own to take care of this. I believe Rails 4 will automatically do this when using has_secure password.

You can try manually lowering the bcrypt cost with the following lines in your spec_helper file:

require 'bcrypt'
Kernel.silence_warnings {
  BCrypt::Engine::DEFAULT_COST = BCrypt::Engine::MIN_COST
}
1 Like

Thanks for all the great feedback/tips everyone!

@joshclayton The specs aren’t particularly slow, but I referenced them because I wanted feedback as a followup to our discussion on Friday about avoiding storing records to the db and general feedback on refactoring specs.

@christoomey I currently use Spork as my preloader, but will check out Zeus and Spring.

@derekprior log_in_as user does behave as you guessed. I’ll look into stubbing it out as well as tinkering with bcrypt.