Ruby on Rails testing

Ruby on Rails / RSpec – you should avoid let and before block

Ruby on Rails / RSpec – you should avoid let and before block February 6, 201814 Comments

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!

Do you have an example where there is no way to get rid of before or let block? Share it with us in the comments section.

Photo by Mateusz Dach from Pexels https://www.pexels.com/photo/blocks-blur-business-daylight-353641/

Update 10/02/2018

I updated the post with a link to the simple benchmark results, I should do this before writing this post. You can view them here. Also many people didn’t understand my point of view properly – I didn’t said 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.

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.

14 comments

    1. I didn’t say that I don’t like them. I like them a lot and I was using them quite a lot.

      Don’t you think that with let and before blocks it is hard to follow TDD properly? There are cases where using before blocks are the right choice but in most cases not.

      In the example you provided, you used context even for a single example – I think is a matter of preference. I just think that if we have one example then documentation output looks way better with a single line and this “if” part. When there are more examples in given context then I always use context tag.
      I think that sticking to DRY rule in tests is not the most important part. Tests are not our production code. If you use let tags then you have to jump from one place to another when looking at the test body. It is hard then to understand the test code and logic.

      I am trying to avoid before blocks because in large applications they are causing randomly failing specs.

      Thanks for your time and explaining your approach. However, I’m not going to change my mind because:

      – with such approach is easy to follow TDD rules and test each execution path only once
      – when you will run specs with `–format documentation` flag it outputs nice documentation
      – you can change one test and you are sure that other test would not fail. With many let tags you can’t simply do this
      – without before blocks you reduce the possibility of randomly failing specs

  1. Honestly, I believe that this is true only for very small unit tests’ files. When you have more than, let’s say, 3 tests with the same context, it doesn’t make any sense to build it again and again from scratches. I don’t think that putting the setup in every test block makes it more readable. It is similar to say that we should put all logic into controllers’ methods to make them more readable. Such projects are extremely hard to maintain so why should we treat test code differently?

    Could you provide any measurements for the second point? I think that this is a factor of milliseconds thus it shouldn’t be an argument in this discussion. For example, we could also say that your approach results in higher memory consumption 😉

    As for the third point, I think that it is more about how your code looks like and if there are many places where you are building the context. If there is a single before block and a few let instructions in the same place, then it would be readable (and also maintainable).

    The last point is in fact not about shared setup or variables but about the code itself. If you have functions that are side-effect free, then there won’t be any dependencies between specs. If you are heavily depending on mutating shared state and making side effects, then your specs will leak (you can even experience that with proposed approach).

    1. It all depends on the complexity of the code that you are testing. If methods are small, let’s say 5 lines like Sandi Metz recommends, then you won’t have much code inside your tests. I don’t have any benchmarks for the second point but it is obvious for me that it would be processed faster than in the version where we are jumping from the test body to let and before sections.

      It may be a matter of a preference, I used to use a lot of let and before blocks but it’s hard to maintain such tests and use TDD approach – but it’s only my opinion.

      Thanks for sharing your point of view 🙂

      1. > I don’t have any benchmarks for the second point but it is obvious for me that it would be processed faster than in the version where we are jumping from the test body to let and before sections.

        Really? 😀 This is an invalid point of view because of two main reasons:
        (1) you can’t simply state that something is true AND put that into an article without checking if you are actually right. It’s just wrong and it can make other developers wrong (especially those who are at the beginning of their programming journey). You have to always check if you are right before publishing something or you should indicate that these are just your thoughts and you didn’t check that (aka learn at your own risk).
        (2) in more complex projects using `let`’s makes your tests suite faster. It depends how faster, but in one project, I have successfully made the whole test suite 4x faster just by re-using the context – https://stackoverflow.com/a/39979170/1941418.

        The only valid point is that, with Ruby, you have to be super careful to not mutate cached objects.

        Don’t get me wrong – it’s totally okay to share your opinion with others, but you should provide measurements for points where you are saying something like “you slow down […]”.

        Btw. consider using Disqus for comments, because now, there is no notification mechanism 😉

        1. > in more complex projects using `let`’s makes your tests suite faster. It depends how faster, but in one project, I have successfully made the whole test suite 4x faster just by re-using the context

          I agree with you, I didn’t write that we should always avoid let’s. I’m using them and before blocks also. But only when I really need them like in the example you provided.

          > You have to always check if you are right before publishing something or you should indicate that these are just your thoughts and you didn’t check that (aka learn at your own risk)

          You are right, I should prepare some proof to confirm my point of view.

  2. I think this is useful only when testing a very small object. Most of the time, the component under testing will have several methods and each one will have a couple branches in logic, which most should be tested. It’s in this case when factories/let/before/contexts become very useful.

    Still a good post to remind us of the importance of keeping things simple

Leave a Reply

Your email address will not be published. Required fields are marked *