DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

How does AI transform chaos engineering from an experiment into a critical capability? Learn how to effectively operationalize the chaos.

Data quality isn't just a technical issue: It impacts an organization's compliance, operational efficiency, and customer satisfaction.

Are you a front-end or full-stack developer frustrated by front-end distractions? Learn to move forward with tooling and clear boundaries.

Developer Experience: Demand to support engineering teams has risen, and there is a shift from traditional DevOps to workflow improvements.

Related

  • Be Punctual! Avoiding Kotlin’s lateinit In Spring Boot Testing
  • Top 10 Advanced Java and Spring Boot Courses for Full-Stack Java Developers
  • Comprehensive Guide to Unit Testing Spring AOP Aspects
  • How to Create a Microservice Architecture With Java

Trending

  • A Complete Guide to Modern AI Developer Tools
  • Exceptions in Lambdas
  • MLOps: Practical Lessons from Bridging the Gap Between ML Development and Production
  • When Caching Goes Wrong: How One Misconfigured Cache Took Down an Entire System
  1. DZone
  2. Coding
  3. Frameworks
  4. Spring Boot - Unit Test your project architecture with ArchUnit

Spring Boot - Unit Test your project architecture with ArchUnit

By 
Anicet Eric user avatar
Anicet Eric
·
Nov. 10, 20 · Tutorial
Likes (6)
Comment
Save
Tweet
Share
11.8K Views

Join the DZone community and get the full member experience.

Join For Free

When building software, it's common for development teams to define a set of guidelines and code conventions that are considered best practices.

These are practices that are generally documented and communicated to the entire development team that has accepted them. However, during development, developers can violate these guidelines which are discovered during code reviews or with code quality checking tools.

An important aspect is therefore to automate these directives as much as possible over the entire project architecture in order to optimize the reviews.

We can impose these guidelines as verifiable JUnit tests using ArchUnit. It guarantees that a software version will be discontinued if an architectural violation is introduced.


ArchUnit is a free, simple and extensible library for checking the architecture of your Java code using any plain Java unit test framework. That is, ArchUnit can check dependencies between packages and classes, layers and slices, check for cyclic dependencies and more. It does so by analyzing given Java bytecode, importing all classes into a Java code structure.  

ArchUnit lets you implement rules for the static properties of application architecture in the form of executable tests such as the following:

  • Package dependency checks
  • Class dependency checks
  • Class and package containment checks
  • Inheritance checks
  • Annotation checks
  • Layer checks
  • Cycle checks

Getting Started

ArchUnit’s JUnit 5 support, simply add the following dependency from Maven Central: 

pom.xml 

XML
xxxxxxxxxx
1
 
1
<dependency>
2
    <groupId>com.tngtech.archunit</groupId>
3
    <artifactId>archunit-junit5</artifactId>
4
    <version>0.14.1</version>
5
    <scope>test</scope>
6
</dependency>


build.gradle 

Groovy
xxxxxxxxxx
1
 
1
dependencies { 
2
  testImplementation 'com.tngtech.archunit:archunit-junit5:0.14.1' 
3
} 


Package Dependency Checks

Java
xxxxxxxxxx
1
24
 
1
class ArchunitApplicationTests {
2
3
    private JavaClasses importedClasses;
4
5
    @BeforeEach
6
    public void setup() {
7
        importedClasses = new ClassFileImporter()
8
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
9
                .importPackages("com.springboot.testing.archunit");
10
    }
11
12
    @Test
13
    void servicesAndRepositoriesShouldNotDependOnWebLayer() {
14
15
        noClasses()
16
                .that().resideInAnyPackage("com.springboot.testing.archunit.service..")
17
                .or().resideInAnyPackage("com.springboot.testing.archunit.repository..")
18
                .should()
19
                .dependOnClassesThat()
20
                .resideInAnyPackage("com.springboot.testing.archunit.controller..")
21
                .because("Services and repositories should not depend on web layer")
22
                .check(importedClasses);
23
    }
24
}


Services and Repositories should not talk to Web layer.

Class Dependency Checks

Java
xxxxxxxxxx
1
20
 
1
class ArchunitApplicationTests {
2
3
    private JavaClasses importedClasses;
4
5
    @BeforeEach
6
    public void setup() {
7
        importedClasses = new ClassFileImporter()
8
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
9
                .importPackages("com.springboot.testing.archunit");
10
    }
11
    @Test
12
    void serviceClassesShouldOnlyBeAccessedByController() {
13
        classes()
14
                .that().resideInAPackage("..service..")
15
                .should().onlyBeAccessed().byAnyPackage("..service..", "..controller..")
16
                .check(importedClasses);
17
    }
18
19
20
}


ArchUnit offers an abstract DSL-like fluent API, which can in turn be evaluated against imported classes. Services should only be accessed by Controllers .

The two dots represent any number of packages (compare AspectJ Pointcuts). 

Naming convention

Java
xxxxxxxxxx
1
37
 
1
class ArchunitApplicationTests {
2
3
    private JavaClasses importedClasses;
4
5
    @BeforeEach
6
    public void setup() {
7
        importedClasses = new ClassFileImporter()
8
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
9
                .importPackages("com.springboot.testing.archunit");
10
    }
11
12
    @Test
13
    void serviceClassesShouldBeNamedXServiceOrXComponentOrXServiceImpl() {
14
        classes()
15
                .that().resideInAPackage("..service..")
16
                .should().haveSimpleNameEndingWith("Service")
17
                .orShould().haveSimpleNameEndingWith("ServiceImpl")
18
                .orShould().haveSimpleNameEndingWith("Component")
19
                .check(importedClasses);
20
    }
21
22
    @Test
23
    void repositoryClassesShouldBeNamedXRepository() {
24
        classes()
25
                .that().resideInAPackage("..repository..")
26
                .should().haveSimpleNameEndingWith("Repository")
27
                .check(importedClasses);
28
    }
29
    @Test
30
    void controllerClassesShouldBeNamedXController() {
31
        classes()
32
                .that().resideInAPackage("..controller..")
33
                .should().haveSimpleNameEndingWith("Controller")
34
                .check(importedClasses);
35
    }
36
    
37
}


A common rule is the naming convention. for example all service class names must end with Service, Component etc.

Annotation Checks

Java
xxxxxxxxxx
1
34
 
1
class ArchunitApplicationTests {
2
3
    private JavaClasses importedClasses;
4
5
    @BeforeEach
6
    public void setup() {
7
        importedClasses = new ClassFileImporter()
8
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
9
                .importPackages("com.springboot.testing.archunit");
10
    }
11
12
    @Test
13
    void fieldInjectionNotUseAutowiredAnnotation() {
14
15
        noFields()
16
                .should().beAnnotatedWith(Autowired.class)
17
                .check(importedClasses);
18
    }
19
    @Test
20
    void repositoryClassesShouldHaveSpringRepositoryAnnotation() {
21
        classes()
22
                .that().resideInAPackage("..repository..")
23
                .should().beAnnotatedWith(Repository.class)
24
                .check(importedClasses);
25
    }
26
    @Test
27
    void serviceClassesShouldHaveSpringServiceAnnotation() {
28
        classes()
29
                .that().resideInAPackage("..service..")
30
                .should().beAnnotatedWith(Service.class)
31
                .check(importedClasses);
32
    }
33
34
}


The ArchUnit Lang API can define rules for members of Java classes. This may be relevant, for example, if methods in a certain context need to be annotated with a specific annotation, or if return types implement a certain interface.

Layer Checks

Java
xxxxxxxxxx
1
25
 
1
class ArchunitApplicationTests {
2
3
    private JavaClasses importedClasses;
4
5
    @BeforeEach
6
    public void setup() {
7
        importedClasses = new ClassFileImporter()
8
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
9
                .importPackages("com.springboot.testing.archunit");
10
    }
11
    @Test
12
    void layeredArchitectureShouldBeRespected() {
13
14
        layeredArchitecture()
15
                .layer("Controller").definedBy("..controller..")
16
                .layer("Service").definedBy("..service..")
17
                .layer("Repository").definedBy("..repository..")
18
19
                .whereLayer("Controller").mayNotBeAccessedByAnyLayer()
20
                .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
21
                .whereLayer("Repository").mayOnlyBeAccessedByLayers("Service")
22
                .check(importedClasses);
23
    }
24
25
}


In Spring Boot app, Service layer depends on Repository layer, Controller layer depend on Service layer.

ArchUnit offers a set of features to assert that your layered architecture is respected. These tests provide automated assurances that access and use is maintained within your defined limits. it is therefore possible to write custom rules. In this article, we have just described a few of the rules. The official ArchUnit guide presents the different possibilities.

The complete source code can be found in my GitHub repository.

unit test Spring Framework Spring Boot Architecture Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Be Punctual! Avoiding Kotlin’s lateinit In Spring Boot Testing
  • Top 10 Advanced Java and Spring Boot Courses for Full-Stack Java Developers
  • Comprehensive Guide to Unit Testing Spring AOP Aspects
  • How to Create a Microservice Architecture With Java

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends: