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:
- Each test should have a single expectation.
- Mock out relationships. Use verifying doubles.
- Don't write to the DB when you don't need to, i.e. use build instead of create.
- 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!