loading...

How to minimize RSpec `describe`/`context` nesting

hanachin profile image Seiei Miyagi ・2 min read

Deeply nested specs

Did you ever write the deeply nested code in RSpec?
I wrote a lot honestly.

In the application code, I avoid to write the deeply nested code. It's hard to read and hard to change, because of bumpy indentation.
When my collegue do code review, they pointing out the deeply nested code.
But in the RSpec, sometime (always) I wrote the deeply nested code and that passed the code review.

For example, like below:

RSpec.describe "Screencast player", type: :system do
  context "When user logged in" do
    let(:user) { create(:user) }

    before do
      login_as(user)
    end

    context "When user register the credit card" do
      let(:card) { create(:card) }

      before do
        user.register_credit_card(card)
      end

      context "When there are screencast" do
        before do
          create(:screencast)
        end

        it "can play the screencast"
      end

      context "When theare not any screencast" do
        it "shows the message, 'no such screencast'"
      end
    end

    context "When user does not register the credit card" do
      it "can not play the screencast"
    end
  end

  context "When user is not logged in" do
    it "can not play the screencast"
  end
end

Why it happened?

RSpec is awesome, it can be nest describe/context forever if you want.
Anything that can go wrong will go wrong. 1
By the way I have the acceptance test written in Gherkin, how it would go?
It's never nested because it doesn't have the syntax for nesting.
https://docs.cucumber.io/gherkin/reference/

How to fix it?

May the Gherkin be with you.

If you writing the specs like Gherkin, the specs behaves like Gherkin.
It never and ever nested, yay!

How to do it?

I use the above example to explain how to do it.

First move arranging preconditions into the shared_context.

RSpec.describe "Screencast player", type: :system do
  shared_context "When user logged in" do
    let(:user) { create(:user) }

    before do
      login_as(user)
    end
  end

  shared_context "When user register the credit card" do
    let(:card) { create(:card) }

    before do
      user.register_credit_card(card)
    end
  end

  shared_context "When there are screencast" do
    before do
      create(:screencast)
    end
  end
end

Then combine them

RSpec.describe "Screencast player", type: :system do
  shared_context "When user logged in" do
    let(:user) { create(:user) }

    before do
      login_as(user)
    end
  end

  shared_context "When user register the credit card" do
    let(:card) { create(:card) }

    before do
      user.register_credit_card(card)
    end
  end

  shared_context "When there are screencast" do
    before do
      create(:screencast)
    end
  end

  describe "user play the screencast" do
    include_context "When user logged in"
    include_context "When user register the credit card"
    include_context "When there are screencast"

    it "can play the screencast"
  end

  describe "user knows there are no screencast yet" do
    include_context "When user logged in"
    include_context "When user register the credit card"

    it "shows the message, 'no such screencast'"
  end

  describe "user can not play the screencast before register the credit card" do
    include_context "When user logged in"

    it "can not play the screencast"
  end

  describe "user can not play the screencast before logged in" do
    it "can not play the screencast"
  end
end

That's it!

Conclusion

Thank shared_context for fixing my deeply nested specs.

And Gherkin is awesome <3


  1. https://en.wikipedia.org/wiki/Murphy%27s_law 

Posted on by:

Discussion

markdown guide
 

I have found myself nesting things more and while I don't necessarily think it's as bad as your colleagues do, this has been a good reminder about shared contexts. Thanks!

 

I don't necessarily think it's as bad as your colleagues do

Thanks👍

We try rubocop-rspec with following setting at new project.
rubocop-rspec.readthedocs.io/en/la...

# .rubocop.yml
RSpec/NestedGroups:
  Max: 2

The specs became so Ghekin-ish 🤣

 

This is a really good idea! Thanks for writing up and sharing.