Unit Test Java POJO in a Smart Way

Sharing is caring!

Introduction

Recently, I was assigned to reduce technical debt in an existing project. One of the keys highlighted technical debt is how low the code coverage is. Therefore, our priority is to increase code coverage in the codebase as much as we can. Luckily (or unluckily), there are many Java POJO in the codebase. The POJO itself covers 50% of the line of code.

Maybe you are wondering why there are so many POJO. We used Java beans heavily. From the DTO, Entity, JSON, XML, Request, Response, etc.

Strategy

You may say it is not critical to unit test POJO. I am one of those who have the same opinion. There is a Lombok framework to do the dirty tasks for creating getter/setter methods. The framework guarantees the constructors and the setter/getter methods are always behave as they are. Therefore, the need to test the POJO is insignificant. However, I have to follow the team’s rules. If they say we need to do testing on POJO, so be it. Therefore, with so many POJO, we need to find a lazy smart way to test it fast, with minimum effort.

Reflection

The whole point of POJO is a simple constructor (default one), a couple of fields declaration, and setter and getter methods. Moreover, this is simply a unit test. We only need to pass the value to test the function/method. Then we get the returned value as expected. So we can send every value that satisfies the compiler. As long as the compiler is happy, we are happy too.

BaseSetterGetter.java

First, we need to specify the POJO classes that will be tested. Next, we create a new instance from each POJO class. And get declared fields from the POJO class using reflections. Then, we set the expected value based on the setter method parameter and invoke the setter. After invoking the setter method, it is time to invoke the getter method to retrieve the return value. Once we get the returned value, we compare it with our expected value.

import org.apache.commons.lang.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.Date;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
@SuppressWarnings("rawtypes")
public class BaseSetterGetterTest {
private final Class[] clazz = new Class[]{
/* YOUR POJO CLASS HERE */
UserResponse.class, ProductEntity.class, ProductDTO.class
};
@SuppressWarnings("unchecked")
public void testSetterGetter(Class[] clazz) {
for (Class aClass : clazz) {
Object instance;
try {
instance = aClass.getDeclaredConstructor().newInstance();
Field[] declaredFields = aClass.getDeclaredFields();
for (Field f : declaredFields) {
try {
String fieldName = f.getName();
if (fieldName.equals("serialVersionUID")) {
continue;
}
String name = StringUtils.capitalize(fieldName);
String getter = "get" + name;
String setter = "set" + name;
Object value = null;
Class<?> fType = f.getType();
if (fType.isAssignableFrom(String.class)) {
value = "str";
} else if (fType.isAssignableFrom(Long.class) || fType.isAssignableFrom(long.class)) {
value = Long.valueOf("1");
} else if (fType.isAssignableFrom(Integer.class) || fType.isAssignableFrom(int.class)) {
value = Integer.valueOf("1");
} else if (fType.isAssignableFrom(Double.class) || fType.isAssignableFrom(double.class)) {
value = Double.valueOf("1.0");
} else if (fType.isAssignableFrom(Date.class)) {
value = new Date();
} else if (fType.isAssignableFrom(Timestamp.class)) {
value = new Timestamp(new Date().getTime());
} else if (fType.isAssignableFrom(Boolean.class)) {
value = Boolean.FALSE;
} else if (fType.isAssignableFrom(boolean.class)) {
value = Boolean.FALSE;
getter = "is" + name;
} // end the rest Type you expect, such as Collection
Method getterMethod = aClass.getMethod(getter);
Method setterMethod = aClass.getMethod(setter, getterMethod.getReturnType());
setterMethod.invoke(instance, value);
Object result = getterMethod.invoke(instance);
assertThat(result).as("in class %s and fields %s ", aClass.getName(), fieldName).isEqualTo(value);
} catch (IllegalArgumentException e) {
System.out.println("aClass = " + aClass);
e.printStackTrace();
throw e;
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
}
// toString test
assertThat(instance.toString()).as("toString in class %s ", aClass.getName()).isNotBlank();
// equals n hashCode test
Set<Object> set = Set.of(instance);
assertThat(set.contains(instance)).as("equals n hashCode in class %s ", aClass.getName()).isTrue();
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
}

As an additional note, in the code sample, I add toString, equals, and hashCode as part of the testing. This is just a simple trick to add coverage as well.

Conclusion

Well, this approach covers almost all my POJO testing problems. However, some cases still need to tackle. For example, I still need to work on parameterized constructor or POJO with a builder pattern. But, it is a small work compared to what already has been covered by this BaseSetterGetter class. If you want to enhance the BaseSetterGetter class, feel free to do it.

That’s all, stay safe everyone.

PS: Please forgive me if you find the code is dirty. My intention is solely for this article. It is only to show how it works, not to achieve code perfection ( if there is any 🙂 )

Author: ru rocker

I have been a professional software developer since 2004. Java, Python, NodeJS, and Go-lang are my favorite programming languages. I also have an interest in DevOps. I hold professional certifications: SCJP, SCWCD, PSM 1, AWS Solution Architect Associate, and AWS Solution Architect Professional.

Leave a Reply

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