Unit testing Java architecture using ArchUnit
In this tutorial, I will show you how to unit test architecture of your Java application. This testing can be helpful when implementing many ideas from various designs(implementing hexagonal architecture, Domain Driven Design, CQRS, etc.)
What is the problem that we want to solve?
Let’s say that we are following Domain Driven Design(DDD) and we want to make sure that our domain is isolated. We want to make sure that all classes that are inside the domain
package are not dependent on classes that are inside the infrastructure
package.
Another example would be that we are following three tier architecture with three layers: web
, application
, and database
. We want to make sure that web
layer depends onthe application
layer, and the application
depends on the database
layer. Let’s write tests for those two examples!
Adding archunit dependencies
Let’s follow the officiel guide:
dependencies {
testImplementation 'com.tngtech.archunit:archunit:1.3.0'
testImplementation("com.tngtech.archunit:archunit-junit5:1.3.0")
}
Important: You need only one of those dependencies. If you use JUnit 5 then you need archunit-junit
dependency, otherwise you need archunit
dependency. In my example, I will use JUnit 5.
Writing archunit test
We can find plenty of great examples here on how to write archunit tests. We can use those examples for our use case:
@AnalyzeClasses(packages = "com.javamentor")
public class ArchitectureTest {
@ArchTest
private final ArchRule isolatedDomainTest =
noClasses().that().resideInAPackage("..domain..")
.should().dependOnClassesThat().resideInAPackage("..infrastructure..");
@ArchTest
private final ArchRule webCannotDependOnDatabaseLayerTest =
noClasses().that().resideInAPackage("..web..")
.should().dependOnClassesThat().resideInAPackage("..database..");
@ArchTest
private final ArchRule databaseCannotDependOnApplicationLayerTest =
noClasses().that().resideInAPackage("..database..")
.should().dependOnClassesThat().resideInAPackage("..application..");
@ArchTest
private final ArchRule loggers_should_be_private_static_final =
fields().that().haveRawType(Logger.class)
.should().bePrivate()
.andShould().beStatic()
.andShould().beFinal()
.because("we agreed on this convention");
}
Besides those “classes from this package should not depend on classes from the other package” I added one more rule that say that Logger
has to be private
and static
. There are plenty of other great examples on how to write good tests such as: “I want to prohibit use of java.util.Date
class(because this class is deprecated)” or “classes with this suffix has to be protected
and final
“, etc. Let’s try to violate this rule in our code and see if test will fail.
package com.javamentor.web;
import com.javamentor.database.PersonDatabase;
public class PersonController {
private PersonDatabase personDatabase;
}
package com.javamentor.database;
public class PersonDatabase {
}
We create two classes and obviously break the rule. If we start our test the result will be:
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'no classes that reside in a package '..web..' should depend on classes that reside in a package '..database..'' was violated (1 times):
Field <com.javamentor.web.PersonController.personDatabase> has type <com.javamentor.database.PersonDatabase> in (PersonController.java:0)
Conclusion
We learned how to unit test architecture of our application. As I said in the beggining, this can be helpful when implementing many ideas from various designs(implementing hexagonal architecture, Domain Driven Design, CQRS, etc.)
Further reading
If you want to learn more about testing, those resources are the best:
- Detailed theoretical explanation and practical implementation of the Test Pyramid is on the Martin Fowler blog here
- Great presentation regarding testing in microservices
- Chapter “Testing patterns” in the Microservices Patterns book by Chris Richardson
- Testing category on my blog