Pages

Monday, 16 November 2009

Demeter Saves Mocking Fairies

Dear Junior
In a Twitter post Damian Guy recently expressed his frustration with some hard-to-unit-test code as “”Every time a mock returns a mock, a fairy dies”. (Twitter @damianguy 2009 Oct 19)
I immediately fell in love with the quote and how it “backwards” refers to a guideline on how to design (or not design) your code for testability. What has struck me recently is that this design guideline is actually nothing but the battle-proven Law of Demeter (“Don’t Talk to Strangers”).
Let me give a rather sketchy example of how you could get a report of the shipping weight of an order composed of order items.
OrderService.reportTotalWeight(order)
orderitemlist= order.items()
weight = 0
for(item in orderitemlist)
weight.add(item.weight);
return weight;
So, what is the problem? Well, imagine the unit test for the report functionality in OrderService. To properly unit test that functionality we need to rig a “test bench” with mocked versions of the “surrounding” objects. If we do not use mocks, we will actually also test the functionality in the classes of the surrounding objects as well, and then we are not really “unit-testing” any longer – more like “micro-integration-testing”.
So a setup would need to pass in a mocked order. But then comes the call “order.items()” upon which the mock need to pass back a list of objects. Further on, these objects will have their “weight” method called, so they need to be mocks as well …
OrderServiceTest.shouldSumOrderItemWeightsForOrderWeightReport()
// Given
order = new Mock()
item1 = new Mock().expect(weight).return(4711)
item2 = new Mock().expect(weight).return(42)
order.expect(items).return([item1,item2])
// When
orderservice.reportTotalWeight(order)
// Then
Messy, messy, messy - and we have a mock that returns mocks. Not good for the fairy population.
The underlying design problem is that the OrderService “reaches beyond” the order object to work on the order item objects. By doing this, it "extends its contact surface" to the rest of the application – and makes it hard to write a simple and complete unit test.
Well, the Law of Demeter is a 20 years old design rule in object orientation that says that an object should only talk to its immediate neighbours. The definition found in e g Wikipedia is that the allowed objects are:
  • The object itself
  • Method parameters
  • Any objects created/instantiated within the method
  • The object’s direct component objects
In many OO-supporting languages build upon C syntax, this can be viewed as “just one dot” – this is not precise, but gives the right intuition about the rule.
And here we see that the order items do not fit into that list from the point of view of the order service.
Redesigning to follow Law of Demeter would limit OrderService to reach to order, but not to order items. This requires a new method in Order: “weight of all items”.
OrderService.reportTotalWeight(order)
weight = order.weighOfAllItems()
return weight
This piece of functionality is obviously trivial to unit test (in case you insist), and will only require you to mock one object, the order.
What has happened is of course that the complexity have moved elsewhere – into the order and its “weight of all items” method.
Order.weightOfAllItems
orderitemlist= this.items()
weight = 0
for(item in orderitemlist)
weight.add(item.weight);
return weight
It is the same code as earlier – with “order” changed to “this” (and some obvious refactoring simplifications). Now unit testing of this only requires one level of mocks, as the code only talks to “its immediate neighbours”.
OrderTest.shouldSumOrderItemWeights ()
// Given
item1 = new Mock().expect(weight).return(4711)
item2 = new Mock().expect(weight).return(42)
// When
order.add(item1).add(item2)
// Then
And no fairies where killed in the testing of this code.
What I find beautiful is that these two ways lead towards the same goal. Law of Demeter was an object orientation theory invention to address coupling and coherence of designs. The save-the-fairies quote came from a pragmatic culture of wanting to produce high-quality code. I just love that they actually mean the same.
Well, when the Law of Demeter was coined, TDD was not around, “mock” meant something else, and fairies was not to be hip for a decade or two. So, finally I must praise Damian for formulating such a modern phrasing of a piece of ancient wisdom.
Yours
Dan
ps To be able to easily write unit tests without neither killing fairies nor "mocking yourself to death" is of course crucial for TDD to be fun.
pps Realising that redesigning your code for testability is of cause an effect of wanting to test the behaviour of the code, not the code per se.