Search code examples
javaannotationsarchunit

ArchUnit - ensure method parameters are annotated


I'm trying to write an ArchUnit test rule, which should ensure that interfaces annotated with @ComponentInterface annotation have method parameters annotated with @Input annotation. Like this:

ArchRule rule =
        methods()
            .that()
            .areDeclaredInClassesThat()
            .areInterfaces()
            .and()
            .areDeclaredInClassesThat()
            .areAnnotatedWith(ComponentInterface.class)
            .should()
            .haveRawParameterTypes(
                allElements(CanBeAnnotated.Predicates.annotatedWith(Input.class)));

The interface looks like this:

@ComponentInterface
public interface AdminComponent {
  void login(@Input(name = "loginString") String loginString);
}

But the test fails with an error like this: Method < com.some.package.AdminComponent.login(java.lang.String)> does not have raw parameter types all elements annotated with @Input in (AdminComponent.java:0)

How should the rule look like to work properly in this case?

P.S. After doing some debug, it turned out that haveRawParameterTypes checks if parameter types (classes) are annotated, not the method parameters itself. So it looks at String class and finds out, that it is not annotated with @Input. Good to know, but it doesn't solve the problem.


Solution

  • You can always fall back to the Reflection API:

    ArchRule rule = methods()
        .that().areDeclaredInClassesThat().areInterfaces()
        .and().areDeclaredInClassesThat().areAnnotatedWith(ComponentInterface.class)
        .should(haveAllParametersAnnotatedWith(Input.class));
    
    ArchCondition<JavaMethod> haveAllParametersAnnotatedWith(Class<? extends Annotation> annotationClass) {
      return new ArchCondition<JavaMethod>("have all parameters annotated with @" + annotationClass.getSimpleName()) {
        @Override
        public void check(JavaMethod method, ConditionEvents events) {
          boolean areAllParametersAnnotated = true;
          for (Annotation[] parameterAnnotations : method.reflect().getParameterAnnotations()) {
            boolean isParameterAnnotated = false;
            for (Annotation annotation : parameterAnnotations) {
              if (annotation.annotationType().equals(annotationClass)) {
                isParameterAnnotated = true;
              }
            }
            areAllParametersAnnotated &= isParameterAnnotated;
          }
          String message = (areAllParametersAnnotated ? "" : "not ")
              + "all parameters of " + method.getDescription()
              + " are annotated with @" + annotationClass.getSimpleName();
          events.add(new SimpleConditionEvent(method, areAllParametersAnnotated, message));
        }
      };
    }