DEV Community

loading...

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

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 

Discussion (4)

Collapse
danielgomezrico profile image
Daniel Gomez

I like to nest things too, I believe that some tests share the same preconditions should be nested, I don't see why it is hard to read, maybe showing some examples on how that is hard to read.

I saw a lot of "plain tests" which are also hard to read, so I don't believe that is a reason to sacrifice this.

Collapse
philnash profile image
Phil Nash

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!

Collapse
hanachin profile image
Seiei Miyagi Author

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 🤣

Collapse
bunufi profile image
Dainius

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