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
Top comments (4)
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.
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!
Thanks👍
We try rubocop-rspec with following setting at new project.
rubocop-rspec.readthedocs.io/en/la...
The specs became so Ghekin-ish 🤣
This is a really good idea! Thanks for writing up and sharing.