Things that slow down rspec tests
published on NOV 1, 2018
There are a lot of things that can slow down your tests - some of them are related to your code and some not. I will focus on quick changes that can make your specs faster and what is more important - better.
Avoid using betruthy and befalsey matchers when you always expect boolean value
Take a look at the code below:
expect(true).to be_truthy expect(1).to be_truthy expect('string').to be_truthy expect(nil).to be_falsey expect(false).to be_falsey
all of these expectations will pass. So you may expect
true to be returned but when something goes wrong and something else is returned then your test may not catch it.
Expect exact value instead:
expect(true).to eq(true) expect(1).to eq(true) expect('string').to eq(true) expect(false)to eq(false) expect(nil).to eq(false)
If you used to think that calling FactoryGirl.build will not create records in the database then stop - https://robots.thoughtbot.com/use-factory-girls-build-stubbed-for-a-faster-test. It will when you have associations declared in your factory. For example if you have such factory:
FactoryGirl.define do factory :user do contact company end end
and you will call
FactoryGirl.build :user it will create two records in the database. If you initialize your factory at the top of the test and you have 10 examples then you will create 20 records - if you don’t need them then you have a huge area for improvements.
FactoryGirl.build_stubbed which will not create any records in the database.
Avoid using Model.new instead of stubbing
Let’s consider following example. We have two simple classes:
class SampleApi def login; end end
class SampleClass def call api.login end private def api SampleApi.new end end
we want to test
require 'spec_helper' describe SampleClass do describe '#call' do it 'calls API' do api = SampleApi.new allow(SampleApi).to receive(:new).and_return(api) allow(api).to receive(:login) sample_class = SampleClass.new sample_class.call expect(api).to have_received(:login).once end end end
it looks good. What if we want to add new method to
SampleApi and then call it before
#login method? Let’s do it:
class SampleApi def login;end def before_login_action;end end
class SampleClass def call api.before_login_action api.login end private def api @api ||= SampleApi.new end end
when we run our test, it’s still green. It’s a bad news because we changed the class implementation. When we are using not stubbed instance we are losing control over executed methods. It may be hard to spot such issue, especially, when method is updated by someone who don’t use Test Driven Development approach.
instance_double instead. What will happen if we replace
api = SampleApi.new line with
api = instance_double(SampleApi, login: double) ? We will receive error:
Double "SampleApi (instance)" received unexpected message :before_login_action with (no args)
Our solution is not slower than previous one and gives us full control. If you combine it with expecting methods to be executed
n times then nothing is able to surprise you.