Search code examples
archunit

Is there any way to enforce a restriction on class and its super class using ArchUnit Test?


I want to enforce a restriction such that methods present in a class that is annotated with Singleton and its SuperClass should not return dataType java.util.date.

Below is the class structure

public interface Demo {

default Date print(){
     return new Date(23456);
  }
}

 

@Singleton
public class Test implements Demo{

public void print(){
     System.out.println("Some string")
  }
}

public class AnotherClass implements Demo{

public void doSomething(){
     System.out.println("do Something")
  }
}
Below is my ArchUnit Test

methods().that().areDeclaredInClassesThat().areAnnotatedWith(Singleton.class)
.should().notHaveRawReturnType(Date.class);

The above test case does not show any violations because it does not consider the method declared in the interface which is implemented by Test Class.

Is there any way by which it will also consider the superclass method as well?

I tried With Java Reflection API Demo.Class.getMethods(), this API considers super class methods as well. I want to achieve the same behavior with ArchUnit


Solution

  • The default method is not declared in Test (but in Demo), therefore no violation is caught when testing methods().that().areDeclaredInClassesThat().areAnnotatedWith(Singleton.class).
    Such a test wouldn't catch a non-default method inhertited from a superclass like in the following example either:

    class Demo {
        Date getDate() {
            return new Date(23456);
        }
    }
    
    @Singleton
    class Test extends Demo {
    }
    

    If you want to cover declared and inherited methods, you can (as suggested by knittl) use a custom condition (but you can at least use getAllRawInterfaces() and getAllRawSuperclasses() – or their convenient combination getAllClassesSelfIsAssignableTo(), which also includes the class itself – to walk the inheritance hierarchy):

    import static com.tngtech.archunit.base.DescribedPredicate.describe;
    import static com.tngtech.archunit.lang.conditions.ArchConditions.have;
    import static com.tngtech.archunit.lang.conditions.ArchConditions.not;
    import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
    import static java.util.stream.Stream.concat;
    
    // ...
    
        classes()
        .that().areAnnotatedWith(Singleton.class)
        .should(not(have(describe("or inherit methods returning Date", javaClass ->
            javaClass.getAllClassesSelfIsAssignableTo().stream()
                .map(JavaClass::getMethods)
                .flatMap(Set::stream)
                .anyMatch(m -> m.getRawReturnType().isEquivalentTo(Date.class))
        ))));
    

    (ArchUnit detects default methods of interfaces without a problem.)