Ruby – Rubocop 25 line block size and RSpec tests

rspecrubocopruby

A typical RSpec unit test makes extensive use of nested Ruby blocks in order to structure the code and make use of DSL "magic" to have specs read like BDD statements:

describe Foo do
  context "with a bar" do
    before :each do
      subject { Foo.new().add_bar }
    end

    it "looks like a baz" do
      expect # etc

In an ideal spec, each example can be relatively short and precise. However it seems usual to have outer blocks grow to 100 lines plus, because the RSpec structure works this way, and doesn't take many spec examples, each of which might have a few lines of specific setup, to get to describe blocks that are the same size or larger than the code for the subject being described.

A recent upgrade of Rubocop brought a new rule into play, that blocks should be no longer than 25 lines. I'm not sure of the rationale for it, because it is not listed in the Ruby style guide. I can see why it could be a good thing, and added to default ruleset. However, after the upgrade, our Rubocop test fails multiple times with messages like tests/component_spec.rb:151:3: C: Block has too many lines. [68/25]

With code metric tools such as Rubocop, I like to have a policy of "Use the defaults, link to the style guide, job done." (mainly because debating tabs vs spaces and other minutiae is wasting time, and IME never gets resolved) Here that is clearly not possible, two of our core data quality tools disagree about code layout approach – or at least that is how I interpret the results, I don't see anything intrinsically wrong with how we've written the specs.

In response, we have simply set the Rubocop block size rule to a high threshold. But that makes me wonder – what am I missing? Is RSpec using a now discredited approach for code layout, and what reasonable options do I have to reduce block sizes in our RSpec tests? I can see ways to restructure the code to avoid large blocks, but they are without exception ugly hacks intended purely to meet Rubocop's rule e.g. break out all the blocks into helper functions:

def looks_like_a_baz
  it "looks like a baz" do
         expect # etc
  end
end

def bar_context
  context "with a bar" do
    before :each do
      subject { Foo.new().add_bar }
    end
    looks_like_a_baz
  end
end


describe Foo do
  bar_context
  # etc

. . . I mean, that's do-able, but turning bunches of spec examples into helper functions in this way seems to be the opposite of the readable approach encouraged by RSpec design.

Is there anything else I can do other than find ways to ignore it?


The closest existing question I could find about this topic here was RSpec & Rubocop / Ruby Style Guide and this looked solvable by editing test templates.

Best Answer

If a specific block is usually too long, I specify it rather than the files

Metrics/BlockLength:
  IgnoredMethods: ['describe', 'context']
Related Topic