Simon Martinelli
Simon Martinelli

Reputation: 36203

Archunit check method calls

I have a class that has three methods that shouldn't be called from certain classes.

How can I check this with Archunit?

So for example

public class Foo {

   public void method1() {
   }

   public void method2() {
   }

   public void method3() {
   }
}

method1 and method2 should only be called by classes Bar1 and Bar2.

Upvotes: 4

Views: 5740

Answers (3)

boly38
boly38

Reputation: 1955

Another design sample I suggest here an example based on annotation and which doesn't need to hard-set class name nor method name.

Need: no service method used by workflow service can be called from outside a workflow.

Action:

  • add @Workflow annotation to you "workflow level"'s classes
  • add @WorkflowMethod to you services level method

Verify:

  • add unit test to verify you design
import static com.tngtech.archunit.core.domain.JavaAccess.Predicates.target;
import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.annotatedWith;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;

import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.junit.ArchUnitRunner;
import com.tngtech.archunit.lang.ArchRule;
import org.junit.runner.RunWith;

import com.example.annotation.Workflow;
import com.example.annotation.WorkflowMethod;

@AnalyzeClasses(packages = "com.example.service")
@RunWith(ArchUnitRunner.class)
public class WorkflowArchTest {

    @ArchTest
    static ArchRule workflow_methods_cant_be_called_from_outside_a_workflow =
            noClasses().that().areNotAnnotatedWith(Workflow.class)
                    .and().haveNameNotMatching(".*(Test|IT)")
                    .should().callMethodWhere(target(annotatedWith(WorkflowMethod.class)));
}

Upvotes: 1

Wim Deblauwe
Wim Deblauwe

Reputation: 26868

I have a very similar requirement and came up with this:

@ArchTest
    public static final ArchRule rule = noClasses()
            .that(not(name(Bar1.class.getName()))
                          .and(not(name(Bar2.class.getName()))))
            .should().callMethodWhere(target(nameMatching("method1"))
                                              .and(target(owner(assignableTo(Foo.class)))))
            .orShould().callMethodWhere(target(nameMatching("method2"))
                                              .and(target(owner(assignableTo(Foo.class)))));

I have not tested it, but should be close I think.

EDIT: imports are:

import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;

import static com.tngtech.archunit.base.DescribedPredicate.not;
import static com.tngtech.archunit.core.domain.JavaCall.Predicates.target;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableTo;
import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name;
import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching;
import static com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With.owner;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;

Upvotes: 5

Manfred
Manfred

Reputation: 3142

With

import com.tngtech.archunit.lang.ArchRule;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
import static com.tngtech.archunit.lang.conditions.ArchConditions.callMethod;

you can use

ArchRule rule = noClasses()
    .that().doNotHaveFullyQualifiedName(Bar1.class.getName())
    .and().doNotHaveFullyQualifiedName(Bar2.class.getName())
    // alternative 1:
    .should().callMethod(Foo.class, "method1")
    .orShould().callMethod(Foo.class, "method2");
    // alternative 2:
    // .should(callMethod(Foo.class, "method1").or(callMethod(Foo.class, "method2")));

Upvotes: 6

Related Questions