rubocop-rspec icon indicating copy to clipboard operation
rubocop-rspec copied to clipboard

RSpec/LetBeforeExamples is not a safe autocorrection

Open gburgett opened this issue 2 years ago • 2 comments

Hello,

I have found an instance where the autocorrection for RSpec/LetBeforeExamples changes the behavior. I believe this cop should be marked as an unsafe autocorrection.

The issue is when a RSpec.shared_examples block defines a let with the same name as one defined closer to the main examples:

# frozen_string_literal: true

Box = Struct.new(:value)

RSpec.shared_examples 'when overriding value' do
  let(:value) { 1 }
end

RSpec.describe 'RSpec/LetBeforeExamples' do
  include_examples 'when overriding value'

  let(:value) { 2 }

  it { expect(value).to eq(2) }
end

In this case, the cop will move the let(:value) above the include_examples call, which changes the behavior.

See example repo here: https://github.com/gburgett/rubocop-let-before-examples

gburgett avatar Sep 20 '22 20:09 gburgett

This over-simplified example shows few bad patterns. First, when overriding value should be a shared context, and not examples, as it only defines the context. Next, the way I'd expect the value to be overridden, is:

RSpec.describe 'RSpec/LetBeforeExamples' do
  include_examples 'when overriding value' do
    let(:value) { 2 }

    it { expect(value).to eq(2) }
  end
end

Darhazer avatar Sep 21 '22 09:09 Darhazer

Hi Darhazer, thanks for the comments. Yes this over-simplified example does have bad patterns, that's primarily because I simplified it significantly from my real-world code in our closed source app. In that app the shared_examples do define some examples but they were not relevant for reproducing this issue so I eliminated them.

Secondly in our code, the solution was to wrap the shared examples in their own describe block so that they did not override the value.

All of that however is irrelevant to the issue described in this PR, which is simply this: LetBeforeExamples autocorrection can in certain circumstances change the behavior of the code.

gburgett avatar Sep 21 '22 13:09 gburgett

Have you considered using it_behaves_like instead of include_examples, @gburgett ? The difference is that the former wraps all shared examples in an example group, and lets defined in it take priority over the ones defined in the including example group.

I'm always opposed to the use of include_examples for no really good reason, as it introduces such hard to discover and understand bugs. And it's not only RuboCop that may introduce such bugs, people, too.

Even though there's a clear unsafety here, I'm more inclined to close this, as the reference code does have have code smells. @bquorning @Darhazer ?

pirj avatar Oct 12 '22 14:10 pirj

Thank you @pirj, yes all our newer code uses it_behaves_like and we do attempt to refactor include_examples when possible.

As a point of argument towards marking this cop as unsafe, it did take me a couple of hours to track down which particular cop was causing my specs to fail. I had upgraded Rubocop and introduced several new cops, ran safe autocorrection on all of them, and then had to work backwards to isolate which particular cop caused the failure.

gburgett avatar Oct 18 '22 17:10 gburgett

I agree with @gburgett on this one. In my opinion a cop is “safe” until you can show one example that it is not.

Could we change the autocorrect to do nothing if the source file includes include_examples?

bquorning avatar Oct 18 '22 17:10 bquorning

@gburgett May I kindly ask you to check if #1413 keeps your specs green after autocorrection?

pirj avatar Oct 19 '22 00:10 pirj

That would work! Thank you very much @pirj

gburgett avatar Oct 19 '22 13:10 gburgett