Today I found 2 schools of thought when it comes to TDD, the “London school of TDD” and the “Chicago school of TDD” - colour me intrigued.
Both approaches(schools) are concerned with mocking dependancies. Consider the following instance method
class Order
def total_including_shipping(shipping_cost)
order_total = CalculateOrderTotal.call(self)
order_total + shipping_cost
end
end
Interaction School of TDD
aka. London School of TDD
Taking an Interaction based approach to testing, we would test that the #total_including_shipping
method calls CalculateOrderTotal.call
with the Order
as an argument. This might look something like this:
it 'calls the CalculateOrderTotal with the shipping cost' do
allow(CalculateOrderTotal).to receive(:call).and_return_original
shipping_cost = 10
order = build(:order)
order.total_including_shipping(shipping_cost)
expect(CalculateOrderTotal).to have_received(:call).with(shipping_cost)
end
In this state the spec would probably need addition test cases as there is nothing checking to overall output of the method.
State School of TDD
aka. Chicago School of TDD
The State based approach to this problem would be to only test the result of the #total_including_shipping
method, something like this:
it 'returns the total including shipping' do
shipping_cost = 10
order = build(:order)
result = order.total_including_shipping(shipping_cost)
expect(result).to eq(42)
end
In this example there are “magic” numbers - there is no obvious reason as to why the method returns 42. What happens if shipping_cost
is changed to 5
🤷♂️
Nanortalik School of TDD
aka. In the middle School of TDD
I stand, not surprisingly, in the middle, Nanortalik, Greenland. In this situation I would test using both approaches. I would assert that both the external dependency was called, with the correct parameters, but I would potentially mock the return value from the call.
With the mocked return value it would be clearer to read, why the #total_including_shipping
returned a specific value.
it 'returns the total including shipping (CalculateOrderTotal + shipping_cost)' do
allow(CalculateOrderTotal).to receive(:call).and_return(20)
shipping_cost = 10
order = build(:order)
result = order.total_including_shipping(shipping_cost)
# The `CalculateOrderTotal` is fully tested in `spec/services/calculate_order_total_spec.rb`
expect(CalculateOrderTotal).to have_received(:call).with(shipping_cost)
expect(result).to eq(30)
end