How to test your Java project’s architecture with ArchUnit

Emre Savcı
Apr 10 · 5 min read

Naming Convention Tests

@RunWith(ArchUnitRunner.class)
@AnalyzeClasses(packages = "com.test.controllers")
public class NamingConventionTests {

    @ArchTest
    ArchRule controllers_should_be_suffixed = classes()
            .that().resideInAPackage("..controllers..")
            .should().haveSimpleNameEndingWith("Controller");
    
}
@RunWith(JUnit4.class)
public class NamingConventionTests {

    @Test
    public void controllers_should_be_suffixed() {
        JavaClasses importedClasses = new ClassFileImporter().importPackages("com.test.controllers");

        ArchRule rule = classes()
                .that().resideInAPackage("..controllers..")
                .should().haveSimpleNameEndingWith("Controller");

        rule.check(importedClasses);
    }
}

Class Location Tests

@RunWith(ArchUnitRunner.class)
@AnalyzeClasses(packages = "com.test")
public class RepositoryPackageTest {

    @ArchTest
    public ArchRule repositories_should_located_in_infrastructure = classes()
            .that().areAnnotatedWith(Repository.class)
            .should().resideInAPackage("..infrastructure..");

}

Method Return Type Tests

@RunWith(ArchUnitRunner.class)
@AnalyzeClasses(packages = "com.test.controllers")
public class ControllerMethodReturnTypeTest {

    @ArchTest
    public ArchRule controller_public_methods_should_return = methods()
            .that().areDeclaredInClassesThat().resideInAPackage("..controllers..")
            .and().arePublic()
            .should().haveRawReturnType(BaseResponse.class)
            .because("here is the explanation");

}

Cyclic Dependency Tests

package com.test.services.slice1;

import com.test.services.slice2.SecondService;

public class FirstService {
    private SecondService secondService;

    public FirstService() {
        this.secondService = new SecondService();
    }
}package com.test.services.slice2;

import com.test.services.slice1.FirstService;

public class SecondService {
    private FirstService firstService;

    public SecondService() {
        this.firstService = new FirstService();
    }
}
@RunWith(ArchUnitRunner.class)
@AnalyzeClasses(packages = "com.test")
public class CyclicDependencyTest {

    @ArchTest
    public static final ArchRule rule = slices().matching("..services.(*)..")
            .should().beFreeOfCycles();

}

Layer Tests

@RunWith(JUnit4.class)
public class LayeredArchitectureTests {

    @Test
    public void layer_dependencies_are_respected() {
        JavaClasses importedClasses = new ClassFileImporter().importPackages("..com.test..");

        ArchRule myRule = layeredArchitecture()
                .layer("Controllers").definedBy("..com.test.controllers..")
                .layer("Services").definedBy("..com.test.services..")
                .layer("Infrastructure").definedBy("..com.test.infrastructure..")
                .whereLayer("Controllers").mayNotBeAccessedByAnyLayer()
                .whereLayer("Services").mayOnlyBeAccessedByLayers("Controllers")
                .whereLayer("Infrastructure").mayOnlyBeAccessedByLayers("Services");

        myRule.check(importedClasses);
    }
}
package com.test.infrastructure;

import com.test.services.SecondService;

public class FirstRepository {
    SecondService secondService;

    public FirstRepository(SecondService secondService) {
        this.secondService = secondService;
    }
}

Wrapping up

freeCodeCamp.org

This is no longer updated. Go to https://freecodecamp.org/news instead

Emre Savcı

Written by

Software Engineer @Trendyol & Software Development Enthusiast | Interested in Java&.Net DDD, CQRS, Event Sourcing, Scalability.

freeCodeCamp.org

This is no longer updated. Go to https://freecodecamp.org/news instead