Test doubles concept in Ruby - Time & Space Complexity
When using test doubles, we want to see how the time to run tests changes as the number of doubles grows.
We ask: How does adding more test doubles affect test execution time?
Analyze the time complexity of the following Ruby code using test doubles.
class OrderProcessor
def initialize(payment_gateway)
@payment_gateway = payment_gateway
end
def process(order)
@payment_gateway.charge(order.amount)
end
end
payment_double = double('PaymentGateway')
allow(payment_double).to receive(:charge).and_return(true)
processor = OrderProcessor.new(payment_double)
processor.process(order)
This code uses a test double to replace a real payment gateway in order processing.
Look for repeated calls or loops involving test doubles.
- Primary operation: Calling the double's method
charge. - How many times: Once per order processed, or once per test run.
As the number of test doubles or calls grows, the total calls to doubles grow linearly.
| Input Size (number of calls) | Approx. Operations |
|---|---|
| 10 | 10 calls to doubles |
| 100 | 100 calls to doubles |
| 1000 | 1000 calls to doubles |
Pattern observation: The number of operations grows directly with the number of calls.
Time Complexity: O(n)
This means the time to run tests grows in a straight line as you add more calls to test doubles.
[X] Wrong: "Test doubles make tests run instantly, no matter how many calls there are."
[OK] Correct: Each call to a double still takes time, so more calls mean more time, even if faster than real objects.
Understanding how test doubles affect test speed helps you write efficient tests and explain your choices clearly.
What if we replaced test doubles with real objects that perform network calls? How would the time complexity change?
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
