Feb 16, 2016

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.

About the Author

Jeff Torson profile.

Jeff Torson

Sr. Consultant

Jeff is a full stack developer with experience in the government/defense and finance industry. He has experience ranging from thick client Eclipse RCP programs to microservices using Spring Boot for data access and Elasticsearch. He enjoys learning about anything related to the IT field and has even managed Linux and Windows servers and setup deployment pipelines. He is a true believer in that if something is worth doing, then it’s worth doing right the first time and fully unit/integration tested.

One thought on “Automatically JUnit Test DTO and Transfer Objects

  1. Levi Liester says:

    This looks like a nice way to avoid writing boring tests. Is there a license with this code? Can I use it freely?

    1. Jeff Torson says:

      Hi Levi. This can be used freely and modified however you want without restriction. I added a licence.txt file to the code repo saying as much.

  2. Ron Jacobs says:

    In testGettersAndSetters(), there’s a comment that items are sorted for consistent test runs. If not sorted, only the ordering of the methods might change each time the test is run, correct ?

    1. Jeff Torson says:

      Correct. I just did that to be consistent in test runs. If two set methods are setup wrong, then I just wanted to make sure that the one that fails first would be the same. Not really needed though.

  3. Tushar says:

    What about GetterSetterPair class i can not see this class .
    Do we suppose to create that our own

    1. Jeff Torson says:

      Sorry about that. The file is in the github repo. I’ll update this page soon to show it as well. You can see the file here.

  4. Mark S says:

    This is great but can you add a test that uses the concept of ignoredFields? I’m not clear on how that works and I think an example would help. Thanks.

  5. Neeraj Sachdeva says:

    Thanks Jeff for inspiring us.
    People looking for GetterSetter class:

  6. raidentrance says:

    I think you need to add the default constructor right?

    public DtoTest() {
    this(null, null);

    because otherwise the EverythingTransferTest has to define a constructor for protected DtoTest(Map<Class, Supplier> customMappers, Set ignoreFields) am I right ?

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Blog Posts
Android Development for iOS Developers
Android development has greatly improved since the early days. Maybe you tried it out when Android development was done in Eclipse, emulators were slow and buggy, and Java was the required language. Things have changed […]
Add a custom object to your Liquibase diff
Adding a custom object to your liquibase diff is a pretty simple two step process. Create an implementation of DatabaseObject Create an implementation of SnapshotGenerator In my case I wanted to add tracking of Stored […]
Keeping Secrets Out of Terraform State
There are many instances where you will want to create resources via Terraform with secrets that you just don’t want anyone to see. These could be IAM credentials, certificates, RDS DB credentials, etc. One problem […]
Validating Terraform Plans using Open Policy Agent
When developing infrastructure as code using terraform, it can be difficult to test and validate changes without executing the code against a real environment. The feedback loop between writing a line of code and understanding […]