ArchUnit - ensure method parameters are annotated
Asked Answered
S

2

5

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.

Scharf answered 5/11, 2020 at 14:18 Comment(2)
Can't find something about parameters neither frankly.. strange.. maybe it's missing.Jaborandi
It seems this is currently not possible. See github.com/TNG/ArchUnit/issues/113 for the relevant issue.Hasten
O
3

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));
    }
  };
}
Ovoviviparous answered 12/7, 2021 at 22:48 Comment(0)
S
3

Note that the information is meanwhile available (but not added to the fluent API yet) -> https://github.com/TNG/ArchUnit/pull/701

You can achieve it with e.g. a custom ArchCondition

methods()
  ...
  .should(haveParametersAnnotatedWith(Input.class))

// ...

private ArchCondition<JavaMethod> haveParametersAnnotatedWith(
  Class<? extends Annotation> annotationType
) {
  return new ArchCondition<JavaMethod>(
    "have parameters annotated with @" + annotationType.getSimpleName()
  ) {
    @Override
    public void check(JavaMethod method, ConditionEvents events) {
      method.getParameters().stream()
        .filter(parameter -> !parameter.isAnnotatedWith(annotationType))
        .forEach(parameter -> events.add(SimpleConditionEvent.violated(method,
          parameter.getDescription() + " is not annotated with @" + annotationType.getSimpleName())));
    }
  };
}
Sunglass answered 12/7, 2022 at 18:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.