Automatically JUnit Test DTO and Transfer Objects
When testing Data Transfer Objects (DTOs) or Transfer Objects you can go a few different routes. If you’re a purist you would say that a unit test should only be written for code that matters. I agree completely with this but when running coverage reports, having code that says it’s 50% covered by tests is a concerning. You then have to dig through the report to see if you actually have good coverage or not. Writing tests which do nothing that test getters and setter are no fun and seem like a waste of time. There are way more interesting problems to solve out there! During some downtime I decided to solve this problem since I got sick of seeing code coverage that wasn’t 90+% when I knew we were at that target if we ignored the DTOs and transfer objects. I decided to spend an hour or two to solve this problem once and for all and came up with a pretty simple solution: Create an abstract test class which does this for me.
When creating this I knew I needed to make this as simple as possible. I needed a way for primitives and common objects to be automatically created. I needed to create the right object for the field/setter, call the setter method with the object, and then verify that the same object is returned when calling the getter. I also needed a way for a user to specify how to create an object if a no-arg constructor isn’t available for non-primitive objects. I created some common creators with the following code and also allow for test classes which extend the class to send in their own custom Supplies or factory methods as well.
As you can tell, I’m using some Java 8 magic for Suppliers here. If I wanted to be more creative, I could use random number generator each time we create an object or even cache the objects as static variables. I’m keeping it simple for this post though. This base test class now has a good set of default mappers and also allows for test classes which extend it to send in custom mappers, if the class that’s being tested is an interface, abstract object, or an object which doesn’t have a no-arg constructor. I also added a some functionality to allow the test class to ignore selected get fields. Some fields may be marked transient but another field is used in its place and performs logic. An example of this could be to ignore a date and time fiend but having a custom getDateTime() function which combines them together but there is no setter. In this case we’d want to send in “getDateTime” as an ignore field and write a manual JUnit test for the method since it’s actually test worthy now.
Now that I have my Suppliers, I can create objects of whatever type with the following method:
So now we have a way to create objects but we need to actually test them. The tricky part here is that we need to use reflection to match the getter and setter together. An issue arises when an object is marked as @Immutable. Normally in this case you’ll have get methods by no setters. This is a valid use case for Hibernate objects where your application shouldn’t be changing anything and Hibernate will set the field via reflection. Not having setters helps reinforce the idea that the object is not mutable so engineers don’t waste time writing code that won’t work. In this case, I use reflection to set the field and then call the getter for code coverage. The following code actually maps everything together via a GetterSetterPair object (don’t worry, a link to the full source is at the bottom of post):
Ahh finally, we see some actual test methods! This method is the only method annotated with a @Test annotation and then we call callGetter() which will actual verify that the object we passed in the same object as the one we set. Why assertSame() instead of assertEquals()? Well we want to make sure the created object is the exact same object we set before and that no shenanigans happened in the setter or getter method. Again, if these method do something special then a normal JUnit test should be written and the getter name should be sent in as an ignore field. Now we just need to test our file:
So we now turned what would have been many boring unit tests which didn’t test any real business logic into a simple file with less than 10 lines of code. All we need to do is extend DtoTest and create a test instance and the DtoTest file will do the rest.
Hopefully this post helps. Although it’s heavy reflection based and may be hard to follow you can run the code as-is and get 100% code coverage just by creating a test class for your DTO and implementing the createInstance() method. Obviously if you have any additional methods (equals(), toString,(), etc), you’ll need to write units tests for those method manually to get 100% coverage for your class. Full code and a test project can be viewed here.
Thanks for reading and hopefully this post helps improve code coverage and maybe save a few poor programmers who are forced to write to manual tests for such simple things.