Last active
January 19, 2021 23:10
-
-
Save arosini/5bb709da5931d600b6b7 to your computer and use it in GitHub Desktop.
JUnit test for 100% code coverage of Lombok's @DaTa annotation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.google.common.reflect.ClassPath; | |
import javassist.CannotCompileException; | |
import javassist.ClassPool; | |
import javassist.CtClass; | |
import javassist.CtConstructor; | |
import javassist.CtMethod; | |
import javassist.CtNewConstructor; | |
import javassist.CtNewMethod; | |
import javassist.NotFoundException; | |
import nl.jqno.equalsverifier.EqualsVerifier; | |
import nl.jqno.equalsverifier.Warning; | |
import org.junit.Assert; | |
import org.junit.Before; | |
import org.junit.Test; | |
import org.meanbean.test.BeanTestException; | |
import org.meanbean.test.BeanTester; | |
import java.io.IOException; | |
import java.lang.reflect.Modifier; | |
// ClassLoader code adapted from http://stackoverflow.com/a/21430849/2464657 | |
public class ModelTests { | |
private static final String MODEL_PACKAGE = "my.package"; | |
private BeanTester beanTester; | |
@Before | |
public void before() { | |
beanTester = new BeanTester(); | |
} | |
@Test | |
public void testAbstractModels() throws IllegalArgumentException, BeanTestException, InstantiationException, | |
IllegalAccessException, IOException, AssertionError, NotFoundException, CannotCompileException { | |
// Loop through classes in the model package | |
final ClassLoader loader = Thread.currentThread().getContextClassLoader(); | |
for (final ClassPath.ClassInfo info : ClassPath.from(loader).getTopLevelClassesRecursive(MODEL_PACKAGE)) { | |
final Class<?> clazz = info.load(); | |
// Only test abstract classes | |
if (Modifier.isAbstract(clazz.getModifiers())) { | |
// Test #equals and #hashCode | |
EqualsVerifier.forClass(clazz).suppress(Warning.STRICT_INHERITANCE, Warning.NONFINAL_FIELDS).verify(); | |
} | |
} | |
} | |
@Test | |
public void testConcreteModels() | |
throws IOException, InstantiationException, IllegalAccessException, NotFoundException, CannotCompileException { | |
// Loop through classes in the model package | |
final ClassLoader loader = Thread.currentThread().getContextClassLoader(); | |
for (final ClassPath.ClassInfo info : ClassPath.from(loader).getTopLevelClassesRecursive(MODEL_PACKAGE)) { | |
final Class<?> clazz = info.load(); | |
// Skip abstract classes, interfaces and this class. | |
int modifiers = clazz.getModifiers(); | |
if (Modifier.isAbstract(modifiers) || Modifier.isInterface(modifiers) || clazz.equals(this.getClass())) { | |
continue; | |
} | |
// Test getters, setters and #toString | |
beanTester.testBean(clazz); | |
// Test #equals and #hashCode | |
EqualsVerifier.forClass(clazz).withRedefinedSuperclass() | |
.suppress(Warning.STRICT_INHERITANCE, Warning.NONFINAL_FIELDS).verify(); | |
// Verify not equals with subclass (for code coverage with Lombok) | |
Assert.assertFalse(clazz.newInstance().equals(createSubClassInstance(clazz.getName()))); | |
} | |
} | |
// Adapted from http://stackoverflow.com/questions/17259421/java-creating-a-subclass-dynamically | |
static Object createSubClassInstance(String superClassName) | |
throws NotFoundException, CannotCompileException, InstantiationException, IllegalAccessException { | |
ClassPool pool = ClassPool.getDefault(); | |
// Create the class. | |
CtClass subClass = pool.makeClass(superClassName + "Extended"); | |
final CtClass superClass = pool.get(superClassName); | |
subClass.setSuperclass(superClass); | |
subClass.setModifiers(Modifier.PUBLIC); | |
// Add a constructor which will call super( ... ); | |
CtClass[] params = new CtClass[] {}; | |
final CtConstructor ctor = CtNewConstructor.make(params, null, CtNewConstructor.PASS_PARAMS, null, null, subClass); | |
subClass.addConstructor(ctor); | |
// Add a canEquals method | |
final CtMethod ctmethod = CtNewMethod | |
.make("public boolean canEqual(Object o) { return o instanceof " + superClassName + "Extended; }", subClass); | |
subClass.addMethod(ctmethod); | |
return subClass.toClass().newInstance(); | |
} | |
} |
This code works fine as long as you don't have the @lombok.RequiredArgsConstructor in the objects.
Is there a way to cover this? or maybe @lombok.AllArgsConstructor ?
This works great except that i am also using lomboks's @builder, it just drops builder in coverage. Has anyone else solved that problem?
@sajadreshi I was able to just use a built in method of the @DaTa class and check if it wasn't null... that covered it for me. like:
var coveredData = myDataAnnotatedDumbUncoveredBuilderObject.toString()
// then assert whatever with coveredData and you'll fulfill the coverage.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I'm trying this out now. I wonder why the author preferred to share this as a snippet instead of sharing it as a project.
Seems to not work for ENUMS.
I changed mine to ignore them: