-
-
Save arosini/5bb709da5931d600b6b7 to your computer and use it in GitHub Desktop.
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(); | |
} | |
} |
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!
@mickknutson the test tries to create a class named YourClassExtended
for each YourClass
. Does your code already contain a class named EquipmentExtended
?
@ilanrosenfeld7 I think that means the class isn't getting covered. Did you change the MODEL_PACKAGE
constant to refer to your package?
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>
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.
Thanks! This is working for me!
Also needed to compile in my project
com.google.guava
guava
14.0
This code works fine as long as you don't have the @lombok.RequiredArgsConstructor in the objects.
- 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
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;
}
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.
I am using this in my Gradle build, and getting this error on all my domain Objects:
The class is as simple as can be:
`
@DaTa
public class Equipment implements Serializable {
String suit;
String container;
String parachute;
String deploymentMethod;
} // The End...
`