ArchUnit Documentation

repository·main·Indexed 25 days ago

https://github.com/tng/archunit

ArchUnit is a Java library for checking the architecture of Java code by analyzing bytecode. It allows developers to automatically test architectural rules—such as package dependencies, layering, cyclic dependencies, and inheritance—using standard Java unit testing frameworks like JUnit. The library provides a fluent API to define rules via ArchRuleDefinition and supports custom violation management through the ViolationStore interface.

Tokens
13.9K
Snippets
40
Records
78
Agent score
87%

What's inside ArchUnit

  1. Overview of ArchUnit modules

    main

    ArchUnit is a library for checking Java code architecture (dependencies, layers, slices, cyclic dependencies) by analyzing bytecode. It is used within standard Java unit testing frameworks.

    Production modules include:

    • archunit: The core infrastructure containing ClassFileImporter, domain objects, and rule syntax.
    • archunit-junit4: Integration for JUnit 4, including ArchUnitRunner for class caching.
    • archunit-junit5-*: Integration for JUnit 5, including API, engine, and engine-api modules.
    • archunit-junit6-*: Integration for JUnit 6, including API, engine, and engine-api modules.
    • archunit-example: Sample code and architecture rules for inspiration.
  2. Overview of ArchUnit

    main

    ArchUnit is a free, simple, and extensible library designed to unit test Java architecture. It allows you to enforce architectural rules by analyzing Java bytecode.

    Key capabilities include:

    • Checking dependencies between packages and classes.
    • Verifying layers and slices.
    • Detecting cyclic dependencies.

    ArchUnit integrates with any plain Java unit test framework.

  3. Understand ArchUnit layers: Core, Lang, and Library

    main

    ArchUnit is structured into three primary layers:

    1. Core: The foundational layer that handles bytecode importing and provides a Java Reflection-like API (e.g., JavaMethod, JavaField). It includes extended concepts for dependency analysis like JavaMethodCall and JavaFieldAccess.
    2. Lang: A fluent API layer used to express architectural rules in an abstract, human-readable way. This is the layer most developers interact with to write tests.
    3. Library: A layer containing complex, predefined rules for common architectural patterns, such as enforcing layered architectures or managing domain slices (e.g., ensuring slices are acyclic).
  4. Understand ArchUnit domain objects and resolution

    main

    ArchUnit represents Java code through a domain model of objects like JavaClass, JavaPackage, JavaMember, and JavaCodeUnit.

    Code Units

    A JavaCodeUnit is an executable block of code, which includes:

    • JavaMethod
    • JavaConstructor
    • JavaStaticInitializer
    • JavaParameter

    Accesses and Resolution

    ArchUnit tracks how code units access one another. At the lowest level, accesses are categorized as:

    • JavaFieldAccess (accessing a field)
    • JavaMethodCall (calling a method)
    • JavaConstructorCall (calling a constructor)

    Because bytecode might reference members in superclasses or interfaces that weren't explicitly imported, ArchUnit uses intermediate targets:

    • FieldAccessTarget
    • MethodCallTarget
    • ConstructorCallTarget

    When you call methods like getAccessesToSelf() on a member, ArchUnit resolves these targets to the actual JavaField, JavaMethod, or JavaConstructor if they are present in the imported graph.

  5. Compare ArchUnit with other architectural tools

    main

    While tools like AspectJ, Checkstyle, or FindBugs can be used to inspect code, ArchUnit offers specific advantages for architectural testing:

    • No New Language: Unlike tools that require learning a specific DSL or pointcut language, ArchUnit rules are written in plain Java.
    • Standard Testing Infrastructure: ArchUnit rules are evaluated using standard unit testing tools like JUnit, requiring no special infrastructure.
    • Extensibility: It provides predefined rules for common cases (like package dependencies) but allows for custom rules that leverage the power of the Java Reflection API to inspect field accesses, method/constructor calls, and subclasses.
    • Deep Inspection: It can inspect code structures (packages, classes, methods) rather than just simple class properties, allowing for complex dependency and inheritance rules.
  6. Understand the benefits of architectural testing with ArchUnit

    main

    ArchUnit allows you to define architectural components and rules directly in code, enabling automatic testing of your system's structure. This approach is particularly useful for:

    • Preventing Architectural Decay: Automatically detect when new features violate established component boundaries or dependency rules.
    • Continuous Integration: Integrate architectural checks into your CI build to ensure compliance with every commit.
    • Agile Alignment: Provide a common language for developers and architects to evolve component structures and rules as the project grows.
    • Onboarding: Help new developers understand the intended architecture through executable code rather than static diagrams.
  7. Check imported classes against an ArchRule

    main

    After defining an ArchRule and importing your classes via ClassFileImporter, you can execute the rule by calling the .check() method on the rule instance, passing in the JavaClasses object.

    JavaClasses importedClasses = new ClassFileImporter().importPackage("com.myapp");
    ArchRule rule = // define the rule
    rule.check(importedClasses);
  8. Install ArchUnit for JUnit 5 & 6

    main

    To use ArchUnit with JUnit 5 or JUnit 6, add the convenience artifact which transitively includes both the API (for writing tests) and the TestEngine (for running tests). Replace {revnumber} with the desired version.

    Gradle:

    dependencies {
        testImplementation 'com.tngtech.archunit:archunit-junit6:{revnumber}'
    }

    Maven:

    <dependency>
        <groupId>com.tngtech.archunit</groupId>
        <artifactId>archunit-junit6</artifactId>
        <version>{revnumber}</version>
        <scope>test</scope>
    </dependency>
    dependencies {
        testImplementation 'com.tngtech.archunit:archunit-junit6:{revnumber}'
    }
  9. Perform Layer Checks

    main

    Define a layered architecture by grouping packages into layers and enforcing access rules between them. Use layeredArchitecture() to define layers and whereLayer() to specify access restrictions.

    Example: Define a three-layer architecture (Controller, Service, Persistence) where:

    • Controller cannot be accessed by any layer.
    • Service can only be accessed by Controller.
    • Persistence can only be accessed by Service.
    layeredArchitecture()
        .consideringAllDependencies()
        .layer("Controller").definedBy("..controller..")
        .layer("Service").definedBy("..service..")
        .layer("Persistence").definedBy("..persistence..")
    
        .whereLayer("Controller").mayNotBeAccessedByAnyLayer()
        .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
        .whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service");
    layeredArchitecture()
        .consideringAllDependencies()
        .layer("Controller").definedBy("..controller..")
        .layer("Service").definedBy("..service..")
        .layer("Persistence").definedBy("..persistence..")
    
        .whereLayer("Controller").mayNotBeAccessedByAnyLayer()
        .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
        .whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service")
  10. Add ArchUnit as a dependency

    main

    To use ArchUnit in your project, add the following dependency to your build configuration. ArchUnit is available via Maven Central.

    Maven

    Add the dependency to your pom.xml with test scope:

    Gradle

    Add the dependency to your dependencies block using testImplementation:

    <!-- Maven -->
    <dependency>
        <groupId>com.tngtech.archunit</groupId>
        <artifactId>archunit</artifactId>
        <version>1.4.2</version>
        <scope>test</scope>
    </dependency>
    
    <!-- Gradle -->
    dependencies {
        testImplementation 'com.tngtech.archunit:archunit:1.4.2'
    }
  11. Run ArchUnit examples via Gradle

    main

    To execute the ArchUnit example tests, which are specifically designed to demonstrate architectural violations (such as incorrect layer dependencies), run the Gradle build with the example property. These tests are marked with @Category(Example.class) and are excluded from the regular build process to prevent them from failing standard CI pipelines.

    ${path_to}/gradlew clean build -P example