Using let_it_be¶
let_it_be comes from the test-prof gem. It creates the record once per example group and reuses it across examples.
It speeds up specs, but it behaves differently from let.
When it is a good fit¶
- A context has many examples and the data can be shared safely.
- Records are read-only, or you can reset state between examples.
- You want to reduce database setup time without changing test behaviour.
Caveats and pitfalls¶
Eager evaluation¶
let_it_be behaves like let! and is evaluated once when the example group is loaded
(right after before(:context)). It is not lazy and not per-example. See "Order of execution" below.
Mutations and cached associations¶
If an example mutates the record or its cached associations without persisting it,
use refind: true so each example gets a fresh reload.
Declaration order¶
let_it_be does not support late initialization. If you reference another let_it_be
inside the block (for example tracker: bug), the referenced let_it_be(:bug) must be defined
earlier in the file. That also means it cannot be used in cases where you change tracker in each example.
Time travel in around(:each)¶
around(:each) runs after let_it_be, so time travel helpers will not affect records
created in let_it_be. Prefer using an explicit time variable.
let(:current_time) { Time.zone.parse("2024-01-01 12:00:00") }
around do |example|
travel_to(current_time) { example.run }
end
let_it_be(:issue) { create(:issue, due_date: current_time + 1.day) }
Small groups¶
If a context has only a few examples, the extra complexity is rarely worth it.
Prefer plain let/let! unless there is a clear performance gain.
What still works without changes¶
Mocks and stubs are not persistent across examples. If you stub methods on an object
created via let_it_be, those stubs are cleared after each example as usual.
Order of execution¶
before(:suite) # 1. Runs once at the very start
↓
before(:context) # 2. Runs once per describe/context block
↓
let_it_be { } # 3. Runs once per describe/context block (after before(:context))
↓
around(:each) - BEFORE # 4. Runs before each test
↓
before(:each) # 5. Runs before each test
↓
let! { } # 6. Evaluated for each test
↓
THE TEST RUNS # 7. Your `it` block executes
↓
after(:each) # 8. Runs after each test
↓
around(:each) - AFTER # 9. Runs after each test
↓
after(:context) # 10. Runs once after all tests in the group
↓
after(:suite) # 11. Runs once at the very end