A home for additional useful unit testing modules for Java.
Note
The project is no longer maintained as most of its functionality can be found nowadays in other libraries. For most parts use ArchUnit. Some methods where moved to Utils4J because they are not strictly bound to test functions.
- 0.12.0-SNAPSHOT = LAST VERSION (Will never be released 😟)
- 0.11.0 (or later) = Java 17 with junit5 / WeldJUnit4Runner removed in favor of weld-junit5
- 0.10.0 = Java 11 with new jakarta namespace
- 0.9.x = Java 11 before namespace change from 'javax' to 'jakarta'
- 0.8.4 (or previous) = Java 8
- Asserting test coverage
- Asserting package dependencies
- Asserting methods are not used
- Assert that JPA entities are valid
- Assert that methods have information if null is allowed or not
- Assert that fields with @JsonbProperty annotation are not final
A good approach is to have at least one test class for every production class.
If you have classes this rule-of-thumb does not apply, you can:
- Write a dummy test class with a comment that describes why not...
- Use an AssertCoverage.ClassFilter to exclude the classes
@Test
public void testCoverage() {
AssertCoverage.assertEveryClassHasATest(new File("src/main/java"));
}
It's a good practice enforcing package dependencies to avoid high coupling and package cycles.
You simply define a dependency description in your "src/test/resources" folder. For an example see units4j.xml).
@Test
public void testAssertDependencies() {
AssertDependencies.assertRules(getClass(), "/units4j.xml", new File("target/classes"));
}
Example: Prevent a java.lang.ArithmeticException Non-terminating decimal expansion; no exact representable decimal result." caused by calling BigDecimal's divide or setScale without a rounding mode:
// Path to '*.class' files
File classesDir = new File("target/classes");
// Can be used to exclude some files/packages
FileFilter fileFilter = new FileFilter() {
@Override
public boolean accept(File file) {
return !file.getPath().contains("my/pkg/to/exclude");
}
};
// Define methods to find
MCAMethod divide = new MCAMethod("java.math.BigDecimal", "java.math.BigDecimal divide(java.math.BigDecimal)");
MCAMethod setScale = new MCAMethod("java.math.BigDecimal","java.math.BigDecimal setScale(int)");
// Fails if any class calls one of the two methods
AssertUsage.assertMethodsNotUsed(classesDir, fileFilter, divide, setScale);
Uses JBoss Jandex to validate JPA entity classes.
import static org.fuin.units4j.JandexAssert.assertThat;
// Collect all class files
File dir = new File("target/classes");
List<File> classFiles = Units4JUtils.findAllClasses(dir);
Index index = Units4JUtils.indexAllClasses(classFiles);
// Verify that all classes annotated with @Entity or @MappedSuperclass observe
// the rules for JPA entities (Class not final + No final methods + ...).
assertThat(index).hasOnlyValidJpaEntities();
It's a good style to define a precondition for method arguments and postconditions for return values of externally used methods. Especially the questions "Can I pass null?" or "Does the method return null values?" is a common source of confusion.
This assertion makes sure that all return values and parameters of all public, protected and package-private methods have at least one of the following annotations:
- @NotNull (For example from Java Validation API) or
- @NotEmpty (For example from Java Validation API) or
- @Nullable (For example from the checkerframework annotation.
The package and case of those annotations does actually not matter as only simple name is checked. It is also possible to pass your own list of expected annotation simple names to the hasNullabilityInfoOnAllMethods()
method.
Example:
public interface MyInterface {
// Post condition says the return value is never null
@NotNull
public Boolean myMethodA();
// Pre condition says the first value cannot be null, but it's OK for the second argument
public void myMethodB(@NotNull Integer abc, @Nullable String def);
// Post condition says the return value may be null
@Nullable
public Long myMethodC();
}
Test:
import static org.fuin.units4j.JandexAssert.assertThat;
// Collect all class files
File dir = new File("target/classes");
List<File> classFiles = Units4JUtils.findAllClasses(dir);
Index index = Units4JUtils.indexAllClasses(classFiles);
// Verify that all methods make a statement if null is allowed or not
assertThat(index).hasNullabilityInfoOnAllMethods();
Verifies that no field that has a @JsonbProperty annotation. The deserialization using a Eclipse Yasson FieldAccessStrategy will fail otherwise silently.
Uses JBoss Jandex to validate JSON-B fields.
import static org.fuin.units4j.JandexAssert.assertThat;
// Collect all class files
File dir = new File("target/classes");
List<File> classFiles = Units4JUtils.findAllClasses(dir);
Index index = Units4JUtils.indexAllClasses(classFiles);
// Verify that no field that has a 'javax.json.bind.annotation.JsonbProperty' annotation.
// The deserialization using a 'org.eclipse.yasson.FieldAccessStrategy' will fail otherwise.
assertThat(index).hasNoFinalFieldsWithJsonbPropertyAnnotation();
Snapshots can be found on the OSS Sonatype Snapshots Repository.
Add the following to your .m2/settings.xml to enable snapshots in your Maven build:
<repository>
<id>sonatype.oss.snapshots</id>
<name>Sonatype OSS Snapshot Repository</name>
<url>http://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>