javaarchunit

How does one write an ArchUnit specification that a constructor or method should never be called?


Certain frameworks require the empty constructor of a class to be present, e.g. a JakartaEE stateless service, but they do not have the appropriate dependencies injected. I would like to annotate these constructors with something like a DoNotUse annotation (with a reason), and then use ArchUnit to check if no one is actually using it.

Something like this:

        ArchRuleDefinition.noConstructors()
                .that()
                .areAnnotatedWith(DoNotUse.class)
                .should()
                .beCalled();

But beCalled or beUsed AFAIK does not exist. I solved it now by adding a never matching condition:

        ArchRuleDefinition.constructors()
                .that()
                .areAnnotatedWith(DoNotUse.class)
                .should()
                .onlyBeCalled()
                .byClassesThat()
                .haveSimpleName("ThisOnPurposeDoesNotMatchAnything");

How do I write a beCalled or the inverse neverBeCalled?


Solution

  • You can use a custom condition using convenient factory methods for ArchConditions and DescribedPredicates:

    import static com.tngtech.archunit.base.DescribedPredicate.describe;
    import static com.tngtech.archunit.lang.conditions.ArchConditions.be;
    
    // ...
    
        ArchRule rule =  ArchRuleDefinition.noConstructors()
                .that().areAnnotatedWith(DoNotUse.class)
                .should(be(describe("called", ctor -> !ctor.getCallsOfSelf().isEmpty())));
    

    If you want more descriptive error messages indicating where the unwanted call happens (cf. comment), you can use a custom ArchCondition like this:

    import static com.tngtech.archunit.lang.SimpleConditionEvent.satisfied;
    
    // ...
    
        ArchRule rule = ArchRuleDefinition.noConstructors()
                .that().areAnnotatedWith(DoNotUse.class)
                .should(new ArchCondition<JavaConstructor>("be called") {
                    @Override
                    public void check(JavaConstructor constructor, ConditionEvents events) {
                        constructor.getCallsOfSelf().forEach(call ->
                            events.add(satisfied(call, call.getDescription()))
                        );
                    }
                });