Saturday, 9 June 2012

Test-driving Builders with Mockito and Hamcrest


A lot of people asked me in the past if I test getters and setters (properties, attributes, etc). They also asked me if I test my builders. The answer, in my case is it depends.

When working with legacy code, I wouldn’t bother to test data structures, that means, objects with just getters and setters, maps, lists, etc. One of the reasons is that I never mock them. I use them as they are when testing the classes that uses them.
For builders, when they are used just by test classes, I also don’t unit test them since they are used as “helpers” in many other tests. If they have a bug, the tests will fail.
In summary, if these data structures and builders already exist, I wouldn’t bother retrofitting test for them.

But now let’s talk about doing TDD and assume you need a new object with getters and setters. In this case, yes, I would write tests for the getters and setters since I need to justify their existence writing my tests first.

In order to have a rich domain model, I normally tend to have business logic associated with the data and have a richer domain model. Let’s see the following example.

In the real life, I would be writing on test at a time, making them pass and refactor. For this post, I’ll just give you the full classes for clarity’s sake. First let’s write the tests:


Now the implementation:


This case is interesting since the Trade object has one property called inboundMessage with respective getters and setters and also uses a collaborator (reportabilityDecision, injected via setter) in its isReportable business method.

A common approach that I’ve seen many times to “test” the setReportabilityDecision method is to introduce a getReportabilityDecision method returning the reportabilityDecision (collaborator) object.

This is definitely the wrong approach. Our objective should be to test how the collaborator is used, that means, if it is invoked with the right parameters and if whatever it returns (if it returns anything) is used. Introducing a getter in this case does not make sense since it does not guarantee that the object, after had the collaborator injected via setter, is interacting with the collaborator as we intended.

As an aside, when we write tests that are about how collaborators are going to be used, defining their interface, is when we are using TDD as a design tool and not just simply as a testing tool. I’ll cover that in a future blog post.

OK, now imagine that this trade object can be created in different ways, that means, with different reportability decisions. We also would like to make our code more readable and we decide to write a builder for the Trade object. Let’s also assume, in this case, that we want the builder to be used in the production and test code as well. 
In this case, we want to test drive our builder. 

Here is an example that I normally find when developers are test-driving a builder implementation.


Now let’s have a look at these tests.  
The good news is, the tests were written in the way developers want to read them. That also means that they were “designing” the TradeBuilder public interface (public methods). 
The bad news is how they are testing it.


If you look closer, the tests for the builder are almost identical to the tests in the TradeTest class. 

You may say that it is OK since the builder is creating the object and the tests should be similar. The only different is that in the TradeTest we instantiate the object by hand and in the TradeBuilderTest we use the builder to instantiate it, but the assertions should be the same, right? 

For me, firstly we have duplication. Secondly, the TradeBuilderTest doesn’t show it’s real intent. 

After many refactorings and exploring different ideas, while pair-programming with one of the guys in my team we came up with this approach:

So now, the TradeBuilderTest express what is expected from the TradeBuilder, that means, the side effect when the build method is called. We want it to create a Trade and set its attributes. There are no duplications with the TradeTest. It is left to the TradeTest to guarantee the correct behavior of the Trade object.

For completion’s sake, here is the final TradeBuider class:



The combination of Mockito and Hamcrest is extremely powerful, allowing us to write better and more readable tests.

2 comments:

Esko Luontola said...

Nice approach in TradeBuilderTest! I'll keep it in mind and try it the next time I write a builder.

DZONEMVB said...

Cool post on Mockito / Hamcrest. Would you be interested in having it promoted on DZOne.com? We've actually produced a Mockito cheat sheet that you might be interested in! You can contact me at egenesky at dzone dot com.

Post a Comment