Ruby on Rails testing

3 things that slow down and make your RSpec tests worse

3 things that slow down and make your RSpec tests worse March 21, 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 be_truthy and be_falsey 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 false or true to be returned but when something goes wrong and something else is returned then your test may not catch it.

Solution?

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)

Avoid using FactoryGirl.build

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.

Solution?

Use 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: SampleClass and SampleApi

class SampleApi
  def login; end
end
class SampleClass
  def call
    api.login
  end

  private

  def api
    SampleApi.new
  end
end

we want to test SampleClass#call method:

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.

Solution?

Use 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.

Download free RSpec & TDD ebook

Do you want to earn more or jump to the next level in your company? Do you know that testing skills are one of the most desired skills? There is only first step: start testing and do it right. My ebook can help you. Subscribe to the newsletter to get a free copy of the book.