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