Archunit test

 ArchUnit integrates nicely with the JUnit test framework, and so, they are typically used together.  All we have to do is add the archunit-junit5 dependency to match our JUnit version:

<dependency> <groupId>com.tngtech.archunit</groupId> <artifactId>archunit-junit5</artifactId> <version>0.14.1</version> <scope>test</scope> </dependency>


Step 1:

The first step is to create a set of Java classes that will be checked for rules violations. We do this by instantiating the ClassFileImporter class and then using one of its importXXX() methods:


JavaClasses jc = new ClassFileImporter() .importPackages("com.baeldung.archunit.smurfs");


Step 2:

 Let’s try to implement the first rule defined above using this API. We’ll use the classes() method as our anchor and add additional constraints from there:

ArchRule r1 = classes()
  .that().resideInAPackage("..presentation..")
  .should().onlyDependOnClassesThat()
  .resideInAPackage("..service..");
r1.check(jc);

Notice that we need to call the check() method of the rule we’ve created to run the check. This method takes a JavaClasses object and will throw an exception if there’s a violation.


Step3:

This all looks good, but we’ll get a list of errors if we try to run it against our code:

java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - 
  Rule 'classes that reside in a package '..presentation..' should only 
  depend on classes that reside in a package '..service..'' was violated (6 times):
... error list omitted

Why? The main problem with this rule is the onlyDependsOnClassesThat(). Despite what we’ve put in the package diagram, our actual implementation has dependencies on JVM and Spring framework classes, hence the error.


One way to solve this error is to add a clause that takes into account those additional dependencies:

ArchRule r1 = classes()
  .that().resideInAPackage("..presentation..")
  .should().onlyDependOnClassesThat()
  .resideInAPackage("..service..", "java..", "javax..", "org.springframework..");

With this change, our check will stop failing. This approach, however, suffers from maintainability issues and feels a bit hacky. We can avoid those issues rewriting our rule using the noClasses() static method as our starting point:

ArchRule r1 = noClasses()
  .that().resideInAPackage("..presentation..")
  .should().dependOnClassesThat()
  .resideInAPackage("..persistence..");

Of course, we can also point that this approach is deny-based instead of the allow-based one we had before. The critical point is that whatever approach we choose, ArchUnit will usually be flexible enough to express our rules.


ArchUnit offers the Library API, a collection of prepackaged rules that address common architecture concerns:

LayeredArchitecture arch = layeredArchitecture()
   // Define layers
  .layer("Presentation").definedBy("..presentation..")
  .layer("Service").definedBy("..service..")
  .layer("Persistence").definedBy("..persistence..")
  // Add constraints
  .whereLayer("Presentation").mayNotBeAccessedByAnyLayer()
  .whereLayer("Service").mayOnlyBeAccessedByLayers("Presentation")
  .whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service");
arch.check(jc);

Here, layeredArchitecture() is a static method from the Architectures class. When invoked, it returns a new LayeredArchitecture object, which we then use to define names layers and assertions regarding their dependencies. This object implements the ArchRule interface so that we can use it just like any other rule.

Comments

Popular posts from this blog

visitor design pattern

Observer design pattern