Test doubles help you pretend parts of your program work in a simple way so you can test other parts easily.
Test doubles concept in Ruby
double('Name', method_name: return_value)
allow(double).to receive(:method_name).and_return(return_value)double('Name') creates a fake object with a name for clarity.
You can set methods and their return values directly or use allow to set expectations.
user = double('User', name: 'Alice') puts user.name # prints 'Alice'
add method always returns 5, no matter the input.calculator = double('Calculator') allow(calculator).to receive(:add).and_return(5) puts calculator.add(2, 3) # prints 5
This program uses a test double to fake a payment gateway. It checks that when pay is called, the fake gateway's charge method returns 'Success'.
require 'rspec/mocks/standalone' class Order def initialize(payment_gateway) @payment_gateway = payment_gateway end def pay(amount) @payment_gateway.charge(amount) end end # Create a test double for payment_gateway payment_gateway = double('PaymentGateway') # Tell the double to expect charge and return 'Success' allow(payment_gateway).to receive(:charge).with(100).and_return('Success') order = Order.new(payment_gateway) result = order.pay(100) puts result
Test doubles do not run real code, so they make tests faster and simpler.
Use descriptive names for doubles to keep tests clear.
You can check if methods were called on doubles to verify behavior.
Test doubles let you replace real parts with simple fake ones for testing.
They help isolate the part you want to test without outside effects.
You can control what doubles return and check how they are used.
Practice
What is the main purpose of using test doubles in Ruby testing?
Solution
Step 1: Understand what test doubles do
Test doubles are fake objects used in tests to replace real parts of the program.Step 2: Identify the main goal
The goal is to isolate the part being tested by removing outside effects from real objects.Final Answer:
To replace real objects with fake ones to isolate tests -> Option CQuick Check:
Test doubles isolate tests = A [OK]
- Thinking test doubles speed up production code
- Believing test doubles permanently change classes
- Confusing test doubles with UI tools
Which of the following is the correct way to create a test double named user in Ruby using RSpec?
?Solution
Step 1: Recall RSpec syntax for test doubles
RSpec uses the methoddoubleto create test doubles.Step 2: Match the correct method call
The correct syntax isdouble('name'), sodouble('user')is right.Final Answer:
user = double('user') -> Option BQuick Check:
RSpec double creation = A [OK]
- Using fake or stub instead of double
- Confusing mock_object with double
- Missing quotes around the name
What will be the output of this Ruby code using a test double?
user = double('user')
allow(user).to receive(:name).and_return('Alice')
puts user.nameSolution
Step 1: Understand the double setup
The doubleuseris told to respond tonamewith 'Alice'.Step 2: Check the method call output
Callinguser.namereturns 'Alice' as set byand_return.Final Answer:
Alice -> Option AQuick Check:
Double returns stubbed value = C [OK]
- Expecting the double name instead of stubbed return
- Thinking it returns nil by default
- Assuming NoMethodError without stub
Identify the error in this test double usage and how to fix it:
order = double('order')
allow(order).to receive(:total)
puts order.totalSolution
Step 1: Analyze the stub setup
The methodtotalis allowed but no return value is set, so it returns nil.Step 2: Fix by adding return value
Addand_return(value)to specify whattotalshould return.Final Answer:
Missingand_returncausesorder.totalto return nil, fix by addingand_return-> Option AQuick Check:
Stub needs return value = B [OK]
- Thinking double name must be symbol
- Confusing allow with expect usage
- Forgetting to print output
You want to test a PaymentProcessor class that calls an external Gateway service. How can you use a test double to ensure your test does not call the real Gateway?
class PaymentProcessor
def initialize(gateway)
@gateway = gateway
end
def pay(amount)
@gateway.charge(amount)
end
end
# Test code here?Solution
Step 1: Understand dependency injection
ThePaymentProcessortakes agatewayobject to callcharge.Step 2: Use a test double for gateway
Create a double that expectschargewith the amount and returns true to simulate success.Step 3: Avoid calling real external service
This prevents the test from calling the realGateway, isolating the test.Final Answer:
Create a double forgatewaythat expectschargewith amount and returns true -> Option DQuick Check:
Use doubles to isolate external calls = D [OK]
- Calling real external services in tests
- Hardcoding dependencies inside classes
- Using allow_any_instance_of unnecessarily
Ready to prove it?
Pass the test and this pattern moves unknown
