You should avoid RSpec let and before blocks

Ruby on Rails / RSpec

Not without a reason before and let blocks were created by RSpec authors but it’s better to avoid them when we don’t really need to use them. Here is why:

  1. You disorder the natural flow of the test by jumping from the test body to the let definition and vice versa.
  2. You slow down your tests as let is transformed into a simple method at the end – memory usage benchmark
  3. Your specs are less readable because you have to jump from one place to another to get the right context
  4. Your specs depend on each other and they should be separated

Let’s consider now an example of tests written with let and before blocks and without:

require 'spec_helper'

describe Users::NameService do
  describe '#name' do
    let(:user) { instance_double(User, posts?: false) }

    it 'returns user name' do
      expect(described_class.new(user).name).to eq(user.name)
    end

    context 'when the user has posts' do
      before { allow(user).to receive(:posts?).and_return(true) }

      it 'returns user name' do
        expect(described_class.new(user).name).to eq("#{user.name} (has posts)")
      end
    end
  end
end

and the same test without before and let:

describe Users::NameService do
  describe '#name' do
    it 'returns user name' do
      user = instance_double(User, posts?: false)

      expect(described_class.new(user).name).to eq(user.name)
    end

    it 'returns user name if the user has posts' do
      user = instance_double(User, posts?: true)

      expect(described_class.new(user).name).to eq("#{user.name} (has posts)")
    end
  end
end

With the second version we have two separated test stories and to understand one of them we don’t have to jump outside of it block. We repeat ourselves a little bit but sometimes readability is more important than using Don’t Repeat Yourself (DRY) rule. Our test is also a little bit faster – it’s more visible in larger tests but it’s always good to make spec as fast as we can without hurting logic and readability. We also get rid of one stub – profit!

Update 10/02/2018

I updated the post with a link to the simple benchmark results, I should do this before writing this post. Also, many people didn’t understand my point of view properly – I didn’t say to always avoid let and before – there are cases when using before and let gives us more power and speed up our specs – especially in complex cases. However, I always try to write a simple code and found such approach more useful.