How test Java code structure in unit tests

Introduction to archunit


<dependency>
    <groupId>com.tngtech.archunit</groupId>
    <artifactId>archunit</artifactId>
    <version>0.19.0</version>
    <scope>test</scope>
</dependency>

ArchUnit

ArchUnit is library for checking the architecture of your Java code. This library available under Apache-2.0 License.

Goals:

Example

class ArchitectureTest {
  private JavaClasses importedClasses;

  @BeforeEach
  void setup() {
    importedClasses = new ClassFileImporter().importPackages("name.mrkandreev.project");
  }

  @Test
  void noClassesShouldAccessStandardStreams() {
    // only logger
    NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS.check(importedClasses);
  }
}

Predefined rules

You can found general predefined rules in com.tngtech.archunit.library.GeneralCodingRules like:

And dependency rules available in com.tngtech.archunit.library.DependencyRules:

Example Spring rules

Require RestController name:

  @Test
  void controllerNameRule() {
    classes()
        .that()
        .areAnnotatedWith(RestController.class)
        .should()
        .haveSimpleNameContaining("Controller")
        .check(importedClasses);
  }

Require @Validated for RestController:

@Test
  void restControllerValidationRule() {
    classes()
        .that()
        .areAnnotatedWith(RestController.class)
        .should()
        .beAnnotatedWith(Validated.class)
        .check(importedClasses);
  }

@Test
  void restControllerValidationRequestBodyRule() {
    classes()
        .that()
        .areAnnotatedWith(RestController.class)
        .should(
            new ArchCondition<>("Any @RequestBody must be @Valid") {
              @Override
              public void check(JavaClass javaClass, ConditionEvents conditionEvents) {
                for (JavaMethod method : javaClass.getMethods()) {
                  if (method.isConstructor()) {
                    continue;
                  }

                  for (Parameter parameter : method.reflect().getParameters()) {
                    if (parameter.isAnnotationPresent(RequestBody.class)
                        && !parameter.isAnnotationPresent(Valid.class)) {
                      conditionEvents.add(
                          new SimpleConditionEvent(
                              javaClass,
                              false,
                              javaClass.getName()
                                  + " contains method "
                                  + method
                                  + " with @RequestBody and without Valid"));
                    }
                  }
                }
              }
            })
        .check(importedClasses);
  }

Require @Configuration classname ends with Config

@Test
  void configurationNameRule() {
    classes()
        .that()
        .areAnnotatedWith(Configuration.class)
        .should()
        .haveSimpleNameEndingWith("Config")
        .check(importedClasses);
  }

You can extend this list, just share your rules: mark.andreev@gmail.com or https://t.me/mrkandreev.


P.S.

Remember about code formatter tools like google-java-format and static analysis tools like pmd, spotbugs, maven checkstyle, google errorprone.


Author Mark Andreev

Machine Learning Engineer