Hi. I am travis and I work in a place long ago abandoned by the gods: New York City. By day, I am a Software Engineer at Patreon. By night, i am a normal person.

RSpec Search & Destroy

There are at least a million blog posts out there about how to write harder, better, faster, and stronger specs, and they all say essentially the same thing:

  1. Each test should have a single expectation.
  2. Mock out relationships. Use verifying doubles.
  3. Don't write to the DB when you don't need to, i.e. use build instead of create.
  4. Use let blocks instead of instance variables.

Now sure, all of these suggestions will make your tests "less brittle" and "more reliable" and "definitely a lot better". However, they won't guarantee that your specs are ultra-dank and absurdly fast (actually #2 and #3 will help) or that they'll make you feel alive again. If you're like me and you feel cold and dead watching your slow test suite churn, there are three simple things you can do.

Delete Them

Yes, the easiest way to speed up your test suite is to just start deleting things. There is a perfectly responsible way to do this: ignore #1. Sometimes tests are just redundant, and sometimes it's fine to make more than one assertion. But how is this possible? Won't we be swallowed up by the earth and spend an eternity in hellfire? Sure, but at least your specs will be faster.

# change this
it 'is ok' do
  expect(subject).to be_ok
end

it 'is JSON' do
  expect(JSON.parse(subject.body)).to_not be_nil
end

it 'has the thing' do
  expect(subject.body).to have_json_path('thing')
end

# to this
it 'is what I want' do
  expect(subject).to be_ok
  expect(subject.body).to have_json_path('thing')
end

Stop Making So Much Data

I really like rule #4 and I suggest you follow it as much as possible. As the Launch Academy author mentions:

Instance variables spring into existence (if they weren't previously defined) as nil. That means typos in before blocks can be nefarious, allowing certain types of tests to pass when they shouldn't.

Still, if you're creating a ton of data before each test it's going to take time, and goddamnit we haven't got time. So, when your test depends on a bunch of data that isn't being tested, create it in a before(:context) block.

before(:context) do
  1000.times { create(:thing) }
end

after(:context) do
  Thing.delete_all
end

Ignore the DB

Rule #3 is a good one, because creating data and writing it to the database adds a ton of overhead that isn't always strictly necessary. In the case of FactoryGirl, using build to set up an object will almost certainly be faster than create, so do that.

Bummer, you have 500 spec files, and it's going to take forever to replace all the instances of create with build and fix all of the resulting failures! Use a script dummy!

# This probably isn't a good idea ¯\_(ツ)_/¯
find spec -iname "*spec.rb" -exec sed -i.bak 's/create/build/g' {} \;

find spec -iname "*spec.rb" -print0 | while IFS= read -r -d $'\0' spec; do
  spring rspec $spec --fail-fast
  if [[ $? -gt 0 ]]; then
    echo "$spec did not pass"
  else
    echo "$spec passed!!!"
    git add $spec
  fi
done

Wow, such specs. Here's a gist of the above for your convenience. Now, just commit the result and push #yolo. Armed with these three new techniques, you will be the ultimate spec slayer. Fly, you fools!

FactoryGirl First Or Create

Rails Jail