Optimising specs for ActiveRecord scopes and other database-dependent functionality

After recently switching to a lower-performance laptop for my regular development tasks, I decided to spend a bit of time cleaning up some long-running rspec examples. I found that some misunderstanding of rspec’s features had led me to write highly inefficient specs.

I’m normally among the first to complain about optimisation chores, but this time the optimisation wasn’t premature: I had a compelling reason to fix these tests, and since I’m not under any pressure at the moment to knock out features, it seemed like a good time to do some learning.

RSpec setting for measuring performance

The first step in my optimisation approach was to modify my spec/spec.opts to use a feature of rspec I didn’t normally use: --format profile shows a list of the slowest examples. You might also want to look at the output of spec --help, especially --example and --format failing_examples.

Fixing scope specs

The specs that were causing the most delay were those that could not make use of stubbing and mocking. That is, the specs for database operations. I was mainly confronted by ActiveRecord scope specs taking up to 20 or so seconds per group. Here is an example of the sort of thing I was doing:

Notice the use of the subject method, which allows the use of the very terse it statements. I’m also using the Machinist object factory for each object creation call (Machinist provides Tutorial.make).

There are two big problems here:

  1. The before block (which is an implicit before(:each)) will run five times (once per example), when it could be run just once
  2. The call to Invoice.arrears will be run five times (once per example), when it could be run just once, after the initial set up

So, before attacking this slow running spec group, I had nine factory calls. Just five database inserts, right? Wrong: each Tutorial.make call also created a chain of further objects, so the database write operations were pretty heavy.

Here’s the fixed version:

In this version, I’ve created a container group called “scopes” that allows me to reuse the subject declaration for other scope specs. The subject declaration is now almost like documentation: “In this group I am always testing a collection of invoices”.

The before block is now a before(:all) block, which means that the block will be run just once for the group of specs. At the end of the block I make the call to Invoice.arrears, so the database read operation is also only performed once.