Don’t prefer `let!` over `let`

Josua Schmid
2 min readSep 14, 2020

This is an answer to the blog post of Jason Swett about the difference of let and let! in RSpec. I felt that it needs some clarification from an RSpec fan and heavy let-user.

let!(…){…} is actually identical to before { let(…) { … } }. So I agree that from your view the control flow is clearer than using let. But this is not the angle I look upon the matter because the main (and here missing) advantage is the possibility to DRY up your specs by overriding your lets together with context like this:

describe 'request XY' do
before do
perform_something(params: { id: id })
end
context 'when id is NOT specified' do
let(:id) { nil }
it { is_expected.to be_successful }
end
context 'when id is out of range' do
let(:id) { 1_000_000 }
it { is_expected.not_to be_successful }
end
context 'when id valid' do
let(:id) { create(:dbobject).id }
it { is_expected.to be_successful }
end
end

You need to get used to this inverted style of reading your tests, but it allows you to reduce the lines of codes immensely (normally only for one cross-cutting concern, but together with shared examples and contexts for even more).

If you agree upon this advantage of let, it starts getting tricky with let! and I’d suggest you not to “always use the let!”. Because you will maneuver yourself into situations where you’re tempted to write code like this:

let!(:logged_in_user) { create(:user) }… do your tests with the logged-in usercontext 'when the user is NOT logged-in' do
let(:logged_in_user) { nil }

end

As soon as this happens, your code is doomed. It’s very difficult for a reader to distinguish between let! and let if he/she needs to debug the test here. In such cases I prefer to use before to make it very clear that logged_in_user is always and in every case initialized.

--

--