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.