RSpec provides let
andlet!
helpers to create data required by the specs.
We can use them as follows:
RSpec.describe Activity, type: :model do
let!(:user) { FactoryBot.create(:user) }
let(:account) { FactoryBot.create(:account) }
end
let
is a memoized method which gets called only when it is referenced in the specs. As it is memoized, if it is called twice, it is not executed again. let!
is called before every spec. It is not memoized like let
.
So in our example above, user is created before every spec, whereas account is created only once and is memoized.
Rspec also has a before
hook which we can be used to execute some code before all the specs are run in a specific block.
RSpec.describe Activity, type: :model do
let!(:user) { FactoryBot.create(:user) }
let(:account) { FactoryBot.create(:account) }
before do
travel_to Time.new(2019, 10, 27, 10, 00, 00)
end
end
We want to set the current time to a specific time for all specs so we added it in the before
block.
In my spec, I was setting such custom time in the before
block and I was expecting user's created_at
time to be set to that custom time. The user was created using let!
just like in the example above. But the user was always getting set with current time as created_at
instead of custom time set in the before
block.
To figure out why this was happening, I decided to see how let!
is implemented. Turns out let!
is implemented as before
hook under the hood. So I have two before hooks.
before do
user = FactoryBot.create(:user)
end
let(:account) { FactoryBot.create(:account) }
before do
travel_to Time.new(2019, 10, 27, 10, 00, 00)
end
These two before hooks get executed serially, so first the user gets created then the time is changed to the custom time.
So the lesson is that, if we want to execute some code in the before block which is going to impact even the creation of the data created in the specs, have it as your first before hook in the spec.
RSpec.describe Activity, type: :model do
before do
travel_to Time.new(2019, 10, 27, 10, 00, 00)
end
let!(:user) { FactoryBot.create(:user) }
let(:account) { FactoryBot.create(:account) }
end
The tip of the day is that prefer having the before
block as first block of the spec, even before defining let
and let!
.
Happy testing.
Top comments (0)