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
Please enter at least three characters to search
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

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

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

  • GitHub Copilot's New AI Coding Agent Saves Developers Time – And Requires Their Oversight
  • MCP Servers: The Technical Debt That Is Coming
  • How To Build Resilient Microservices Using Circuit Breakers and Retries: A Developer’s Guide To Surviving
  • Monolith: The Good, The Bad and The Ugly
  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.7K 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
Oops! Something Went Wrong

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
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!