Skip to main content
In a project, a well-structured architecture is one of the fundamental pillars for achieving sustainable development over time. Today, in this Inspiring Technology article, our Hunters share everything we need to know to achieve this: learn about ArchUnit.
13/10/2023
archunit

In addition to defining an architecture, it is necessary to ensure that it is applied to the project. Initially, implementation is usually carried out in accordance with the proposed architecture, but over time, the complexity of the functionalities increases, new developers are added, and experienced developers leave; this often causes the project to deviate from it. Verifying that the architecture is adhered to in each change and development made within the project is a manual task performed by people with extensive knowledge. Often, due to lack of time, these reviews cannot be performed exhaustively and take up time that could be dedicated to other, more specific reviews.

ArchUnit is a library that allows you to define automated tests to ensure that a project adheres to its intended architecture. These tests are easily defined and run quickly as JUnit tests. It also simplifies code reviews, allowing them to focus on more specific aspects and, in turn, helps less experienced developers understand the project's architecture, allowing them to check at any time whether the changes they have made are correct from this perspective. To operate the test, architectural rules are defined using the ArchUnit API. During testing, the generated bytecode is analyzed and compliance with the rules is verified.

Integration into a project

ArchUnit integrates with test execution, as it has a native JUnit implementation, allowing for quick and easy adoption in most projects. It has explicit support for JUnit 4 and JUnit 5, although it can also be used with any test automation framework running on the Java virtual machine.

To start, you need to add the library as a dependency to your project.

archunit

JUnit dependency with Maven project manager.

Once the library is included, the automated tests are defined as JUnit tests with the following peculiarities:

  • The base package containing the classes to be verified is indicated with the @AnalyzeClasses annotation.
  • Test methods are indicated with the @ArchTest annotation instead of @Test.
  • Test methods annotated with @ArchTest receive as a parameter a JavaClasses object, which will contain references to the classes on which the tests will be performed.
     
archunit

ArchUnit test structure with JUnit.

Common architecture checks

ArchUnit offers a set of predefined rules to facilitate the verification of the correct use of n-tier, onion, or hexagonal architectures. To illustrate these rules, we consider an example project with an n-tier architecture, where the Presentation layer accesses the Service layer, which in turn accesses the Persistence layer. In this case, for example, access to the Presentation layer by methods in the Service or Persistence layers is restricted.
 

archunit

Diagram with definition of relationship between layers as an example.

To ensure that these restrictions are met, the following test is defined:

archunit

Diagram with definition of relationship between layers as an example.

This table names each layer and indicates which package it resides in. The constraints that must be met between layers are then defined:

  • No layer can access the Presentation layer.

  • The Service layer can only be accessed by the Presentation layer.

  • The Persistence layer can only be accessed by the Service layer.

Thus, if any of these restrictions are not met, such as if a service layer method depends on a presentation layer method, this automated test will fail.

ArchUnit API

ArchUnit offers a simple API that allows for clear definition of architectural rules for test definition.

To define a rule, you select the unit to which it applies, allowing you to choose between classes, methods, constructors, members, fields, and the negated versions of each (for example, noClasses). Next, you enter the filters you want to set, and finally, the checks.

The following example checks that no classes that do not reside in the services, persistence, or mappers package depend on classes that reside in the entities package.

archunit

Example definition of test.

Service structure use case

ArchUnit offers tools for defining rules tailored to project specifications. The following example implements the following rules for the services of a Spring Boot project:

  • Service interfaces are defined in the services package and their implementations are defined in the services.impl package.

  • The client code will always depend on the service interface, never on the service itself.

  • Only service classes reside within the services.impl package.

  • No other application package contains more service classes.

archunit

Example test package composed only of interfaces.
 

To ensure that the restrictions are met, a rule is first defined that checks that only interfaces are defined in the services package.

archunit

Example test package composed only of classes.

A rule is defined to ensure that classes residing in service.impl are not directly depended upon at any point. This way, service interfaces will be depended upon, and the framework will be responsible for injecting the corresponding class.

archunit

Example test does not depend on service classes.

A rule is defined to ensure that the service.impl package is composed exclusively of services , i.e. classes annotated with _@Service_.
 

archunit

Example test package composed only of classes.

Finally, a rule is defined to ensure that no class annotated with _@Service_ exists outside the service.impl package.

 

archunit

Example testing services outside the services.impl package.
 

Descriptive error messages

If a rule is violated , ArchUnit generates a readable error text to help understand the problem . Additionally, a text can be associated with a rule to explicitly indicate the reason for it.

In the following sample test, a rule is defined that checks if any class annotated with @RestController exists outside of the controllers package.

archunit

Example testing services outside the services.impl package.
 

Below is an error message produced when this rule fails. For this test, the @RestController annotation was added to the ClientDTO class, which is outside the controllers package.

archunit

Example error message.

As you can see, ArchUnit creates a text explaining the rule, followed by the text with the reasoning. At the end of the error message , the exact location and explanation of the problem are explicitly stated.

ArchUnit in C# projects

ArchUnit can run on any project that uses Java virtual machine-based languages , such as Kotlin, Scala, etc.

To allow its use in projects that use the C# language, there is a fork called ArchUnitNet .

Advantages

  • It allows to ensure compliance with the architecture defined for the project in an automated manner .

  • Help people new to the project understand the architecture .

  • Simplify code reviews.

  • Compatible with Java virtual machine based languages .

  • Fast test execution .

  • Simple definition of testing .

Disadvantages

ArchUnit analyzes bytecode , complicating tests on code generators like Lombok or MapStruct .

Conclusions

Using ArchUnit can have a significant impact on a project, ensuring compliance with the planned architecture, facilitating code review for experienced developers, and easing the introduction of new developers. Furthermore, it can be easily implemented as integrated unit tests within the project.

Below, we can see a video of its operation :

Want to know more about Hunters?

Being a Hunter means accepting the challenge of testing new solutions that deliver differentiated results. Join the Hunters program and become part of a cross-functional group capable of generating and transferring knowledge.

Get ahead of the digital solutions that will drive our growth. Find more information about Hunters on the website.

LinkedIn Joan Galiana Joan Galiana - Software Technician at Altia