Search code examples
javaarchunit

Archunit enforce test coverage


How can I enforce using Archunit that every class has a correspondent test class?

I'm able to get all the class that I want to check using this Rule:

        classes().that()
                 .areNotInterfaces()
                 .and()
                 .areNotAnonymousClasses()
                 .and()
                 .areNotRecords()
                 .and()
                 .areNotEnums()
                 .should()

But not sure where to go from here.

Thanks


Solution

  • You can implement a "non-local" ArchCondition (i.e. one that relies on all objects to test) by overwriting its init(Collection<T> allObjectsToTest) method.

    ArchUnit's library of GeneralCodingRules has a testClassesShouldResideInTheSamePackageAsImplementation(), whose implementation is a good starting point to come up with something like this:

    @ArchTest
    static final ArchRule relevant_classes_should_have_tests =
            classes()
                .that()
                    .areTopLevelClasses()
                    .and().areNotInterfaces()
                    .and().areNotRecords()
                    .and().areNotEnums()
                .should(haveACorrespondingClassEndingWith("Test"));
    
    private static ArchCondition<JavaClass> haveACorrespondingClassEndingWith(String testClassSuffix) {
        return new ArchCondition<JavaClass>("have a corresponding class with suffix " + testClassSuffix) {
            Set<String> testedClasseNames = emptySet();
    
            @Override
            public void init(Collection<JavaClass> allClasses) {
                testedClasseNames = allClasses.stream()
                        .map(JavaClass::getName)
                        .filter(className -> className.endsWith(testClassSuffix))
                        .map(className -> className.substring(0, className.length() - testClassSuffix.length()))
                        .collect(toSet());
            }
    
            @Override
            public void check(JavaClass clazz, ConditionEvents events) {
                if (!clazz.getName().endsWith(testClassSuffix)) {
                    boolean satisfied = testedClasseNames.contains(clazz.getName());
                    String message = createMessage(clazz, "has " + (satisfied ? "a" : "no") + " corresponding test class");
                    events.add(new SimpleConditionEvent(clazz, satisfied, message));
                }
            }
        };
    }