Many developers have a hard time wrapping their head around RSpec’s DSL syntax. To me for a long time, RSpec’s syntax was a mystery.
Then one day I realized that RSpec’s syntax is just methods and blocks. There’s not some crazy ass metaprogramming going on that’s beyond my abilities to comprehend. Everything in RSpec I’ve ever used can be (as far as I can tell) boiled down to methods and blocks.
I’ll give a concrete example to aid understanding.
Below is an RSpec test I pulled from a project of mine, Mississippi.com, which I’ve live-coded in my Sausage Factory videos.
require 'rails_helper'
RSpec.describe Order, type: :model do
subject { build(:order) }
describe 'validations' do
it { should validate_presence_of(:customer) }
end
describe '#total_cents' do
it 'returns the total amount for the order' do
order = create(
:order,
line_items: [
create(:line_item, total_amount_cents: 5000),
create(:line_item, total_amount_cents: 2500)
]
)
expect(order.total_cents).to eq(7500)
end
end
end
Now here’s the same test with all the optional parentheses left in instead of out. What might not have been obvious to you before, but is made clear by the addition of parentheses, is that describe
and it
are both just methods.
Like any Ruby method, it
and describe
are able to accept a block, which of course they always do. (If you don’t have a super firm grasp on blocks yet, I might suggest reading up on them and then writing some of your own methods which take blocks. I went through this exercise myself recently and found it illuminating.)
In addition to putting in all optional parentheses, I changed every block from brace syntax to do
/end
syntax. I think this makes it more clear when we’re dealing with a block versus a hash.
require('rails_helper')
RSpec.describe(Order, { type: :model }) do
subject do
build(:order)
end
describe('validations') do
it do
should(validate_presence_of(:customer))
end
end
describe('#total_cents') do
it('returns the total amount for the order') do
order = create(
:order,
line_items: [
create(:line_item, total_amount_cents: 5000),
create(:line_item, total_amount_cents: 2500)
]
)
expect(order.total_cents).to(eq(7500))
end
end
end
The latter method is runnable just like the former version (I checked!) because although I changed the syntax, I haven’t changed any of the functionality.
I hope seeing these two different versions of the same test is as eye-opening for you as it was for me.
Lastly, to be clear, I’m not suggesting that you permanently leave extra parentheses in your RSpec tests. I’m only suggesting that you temporarily add parenthesis as a learning exercise.
This really does make the syntax more readable and tangible. As a ‘newer’ to Rails programmer I have been stumbling through some of the bits that the seasoned developers present ( HAML vs erb, Rspec DSL …) Each time I found myself trying to keep up I realized that going back to the basic syntax ie. erb instead of HAML removed the barrier of figuring out the idiosyncrasies that the “advanced” syntax required and I was able to get more proficient with the basics. I see the advantages of using the progressive syntax after you master the basic. Something as simple as putting the optional parentheses back into the Rspec syntax is definitely worth my while until it becomes more second nature.
This is so great. I’ve never have a problem to understand the RSpec syntax and really later on understood it was just blocks but this approach makes it even more understandble.
It all makes sense. I’m used to use parentheses in methods like `eq()`, `FactoryBot.build()` so that I can read better specs, as well.
Thanks for sharing
Thank you!