Skip to content

Instantly share code, notes, and snippets.

@arosini
Last active January 19, 2021 23:10
Show Gist options
  • Save arosini/5bb709da5931d600b6b7 to your computer and use it in GitHub Desktop.
Save arosini/5bb709da5931d600b6b7 to your computer and use it in GitHub Desktop.
JUnit test for 100% code coverage of Lombok's @DaTa annotation
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();
}
}
@mickknutson
Copy link

mickknutson commented Aug 21, 2016

I am using this in my Gradle build, and getting this error on all my domain Objects:

CannotCompileException: by java.lang.LinkageError: loader (instance of sun/misc/Launcher$AppClassLoader): attempted duplicate class definition for name: "pid/domain/incident/EquipmentExtended"

The class is as simple as can be:

`
@DaTa
public class Equipment implements Serializable {
String suit;
String container;
String parachute;
String deploymentMethod;

//--- Common methods ----------------------------------------------------//
// NOTE: Handled by Lombok

} // The End...
`

@ilanrosenfeld7
Copy link

I still get a red label over @DaTa annotation, which makes my branch coverage to be to low. Do you know how to cover that?
Thanks!

@arosini
Copy link
Author

arosini commented Nov 21, 2016

@mickknutson the test tries to create a class named YourClassExtended for each YourClass. Does your code already contain a class named EquipmentExtended?

@arosini
Copy link
Author

arosini commented Nov 21, 2016

@ilanrosenfeld7 I think that means the class isn't getting covered. Did you change the MODEL_PACKAGE constant to refer to your package?

@vincenzovitale
Copy link

vincenzovitale commented Jan 25, 2017

the list of dependencies to add:

        <dependency>
            <groupId>org.meanbean</groupId>
            <artifactId>meanbean</artifactId>
            <version>2.0.3</version>
        </dependency>
        <dependency>
            <groupId>nl.jqno.equalsverifier</groupId>
            <artifactId>equalsverifier</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.19.0-GA</version>
            <scope>test</scope>
        </dependency>

@arosini
Copy link
Author

arosini commented Jan 27, 2017

Thanks @vincenzovitale. Just wondering did you get this to work? I wrote it a while ago and I assume Lombok has changed a bit. Just wondering if I should update it.

@dashaun
Copy link

dashaun commented Mar 9, 2017

Thanks! This is working for me!

@thomas-hutterer-tik
Copy link

Also needed to compile in my project

com.google.guava
guava
14.0

@Thadir
Copy link

Thadir commented Oct 25, 2017

This code works fine as long as you don't have the @lombok.RequiredArgsConstructor in the objects.

@bbottema
Copy link

bbottema commented Feb 15, 2018

  • This doesn't work if your concrete models contains fields declared with an abstract supertype. You'll have to configure the BeanTester of the meanbean library to account for this
  • This also doesn't work if your model class extends a superclass without adding new fields. For JPA for example this can be useful. You'll have to manually exclude those classes from this automated test
  • This also doesn't work for a model structure which contains a circular dependency, which is often the case for JPA entities. You will have to register a custom instance factory with the EqualsVerifier for one of the types to break the cycle

@menelaosbgr
Copy link

menelaosbgr commented Mar 14, 2018

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:

    if (Modifier.isAbstract(modifiers) || Modifier.isInterface(modifiers) || clazz.equals(this.getClass()) || clazz.isEnum()) {
                continue;
            }

@sajjadsalehi
Copy link

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 ?

@sajadreshi
Copy link

This works great except that i am also using lomboks's @builder, it just drops builder in coverage. Has anyone else solved that problem?

@archae0pteryx
Copy link

@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