What if you never had to repeat setup code in your tests again?
Why Let and before hooks in Ruby? - Purpose & Use Cases
Imagine you are testing a program and need to create the same setup again and again for each test. You write the same code to prepare data or objects before every test manually.
This manual way is slow and boring. You might forget to set up something or make mistakes copying code. It makes tests messy and hard to fix when changes are needed.
Using let and before hooks in Ruby tests helps you prepare data and setup once, then reuse it automatically in each test. This keeps your tests clean, easy to read, and less error-prone.
def test_something user = User.new user.name = 'Alice' # test code here end
let(:user) { User.new(name: 'Alice') }
before { user.prepare! }
# test code uses user directlyYou can write clear, DRY (Don't Repeat Yourself) tests that run smoothly and are easy to maintain.
When testing a shopping cart, you can use let to create a cart with items and before to apply discounts before each test, so you don't repeat setup code every time.
Manual setup for tests is slow and error-prone.
let and before hooks automate setup and keep tests clean.
This makes tests easier to write, read, and maintain.
Practice
let in Ruby tests?Solution
Step 1: Understand
letbehaviorletdefines a value that is created only when needed (lazy evaluation) and reused within the same test.Step 2: Differentiate from
beforebeforeruns code before each test but does not create reusable values likelet.Final Answer:
To create reusable values that are lazily evaluated -> Option AQuick Check:
let= reusable lazy value [OK]
- Confusing let with before hook
- Thinking let runs before each test eagerly
- Assuming let defines test cases
let variable named user with value "Alice"?Solution
Step 1: Recall
letsyntaxlettakes a symbol argument and a block returning the value:let(:name) { value }.Step 2: Check each option
let(:user) { "Alice" } matches correct syntax. Options B and D miss parentheses or block syntax. before { user = "Alice" } usesbefore, notlet.Final Answer:
let(:user) { "Alice" } -> Option CQuick Check:
Correctletsyntax uses symbol and block [OK]
- Omitting parentheses around symbol
- Using assignment instead of block
- Confusing let with before syntax
let(:count) { 0 }
before { count += 1 }
it "increments count" do
puts count
endSolution
Step 1: Understand
letandbeforeinteractionlet(:count) { 0 }defines a method returning 0. Butcountis not a variable; it's a method call.Step 2: Analyze
before { count += 1 }count += 1tries to modifycountas a variable, butcountis a method, so Ruby raises an error: undefined method+=for nil or no local variable.Final Answer:
Error: undefined method += for nil -> Option DQuick Check:
Cannot modify let method return with += [OK]
- Assuming let creates a variable to modify
- Expecting count to increment automatically
- Ignoring that let is lazily evaluated method
let(:numbers) { [1, 2, 3] }
before do
numbers << 4
end
it "checks numbers length" do
expect(numbers.length).to eq(4)
endSolution
Step 1: Understand let and before execution order
let(:numbers)memoizes a new array [1,2,3] the first timenumbersis called in each test example.Step 2: Trace the test execution
Inbefore,numbers << 4calls/memoizesnumbersand mutates it to [1,2,3,4]. Init,numbers.length(memoized) is 4, so test passes.Final Answer:
No error; test passes as expected -> Option BQuick Check:
Mutation inbefore(:each)affects per-example memoizedletcorrectly [OK]
- Misunderstanding per-example memoization (no inter-test sharing)
- Thinking mutation always causes shared state between tests
- Confusing
before(:each)withbefore(:all)
let and before hooks?let(:user_name) { "Bob" }
before do
@user = User.new(name: user_name)
@user.activate!
end
it "checks user is active" do
expect(@user.active?).to be true
endWhat is the main advantage of this approach?
Solution
Step 1: Analyze separation of concerns
let(:user_name)defines reusable data lazily, whilebeforeruns setup code creating and activating user.Step 2: Understand benefits
This keeps data definition and setup logic cleanly separated, improving test readability and maintainability.Final Answer:
Separates reusable data (user_name) from setup actions (before) -> Option AQuick Check:
let for data, before for setup [OK]
- Thinking let runs before before hook
- Avoiding instance variables unnecessarily
- Confusing reset behavior of let
Ready to prove it?
Pass the test and this pattern moves unknown
