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

Object Partners profile.

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 ?

  7. Mikko Östlund says:

    Great thing!
    I believe that you have to put the “builder.putAll(…)” statements (in the DtoTest constructor)
    in the appropriate order. Shouldn’t they be ordered as follows?
    (As of this writing they are in opposite order).

  8. Mikko Östlund says:

    How I wish this were available on Maven Central…

Leave a Reply

Your email address will not be published.

Related Blog Posts
Natively Compiled Java on Google App Engine
Google App Engine is a platform-as-a-service product that is marketed as a way to get your applications into the cloud without necessarily knowing all of the infrastructure bits and pieces to do so. Google App […]
Building Better Data Visualization Experiences: Part 2 of 2
If you don't have a Ph.D. in data science, the raw data might be difficult to comprehend. This is where data visualization comes in.
Unleashing Feature Flags onto Kafka Consumers
Feature flags are a tool to strategically enable or disable functionality at runtime. They are often used to drive different user experiences but can also be useful in real-time data systems. In this post, we’ll […]
A security model for developers
Software security is more important than ever, but developing secure applications is more confusing than ever. TLS, mTLS, RBAC, SAML, OAUTH, OWASP, GDPR, SASL, RSA, JWT, cookie, attack vector, DDoS, firewall, VPN, security groups, exploit, […]