Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check access classes only works for some cases #1311

Open
ramon2 opened this issue May 28, 2024 · 1 comment
Open

Check access classes only works for some cases #1311

ramon2 opened this issue May 28, 2024 · 1 comment

Comments

@ramon2
Copy link

ramon2 commented May 28, 2024

Hello, I have encountered strange behavior in one of the architecture tests that checks that no class from the 'application' layer can use classes from the 'infrastructure' layer. In some cases, the test fails (desired behavior), but in other cases, the test does not fail (undesired behavior).

I would like to know if this is an error in the library or if I am coding the tests incorrectly. The test is as follows:

package com.adevinta.archtests

import ...

@AnalyzeClasses(
    packages = ["com.adevinta.archtests"],
    importOptions = [ImportOption.DoNotIncludeTests::class]
)
class HexagonalArchitectureTest {
    @ArchTest
    val `the domain classes should not access the application classes` =
        noClasses()
            .that()
            .resideInAPackage("..application..")
            .should()
            .accessClassesThat()
            .resideInAnyPackage("..infrastructure..")
}

Here is a highly simplified example to illustrate the behavior I'm referring to.
Case 1: If I print a field from the 'infrastructure' layer class in my 'application' layer class, then the test fails (desired behavior).

package com.adevinta.archtests.application

import com.adevinta.archtests.infrastructure.controller.CarController

class CarUseCase {

    fun execute(carController: CarController) {
        println("CarUseCase: ${carController.name}")
    }
}

Case 2: If I print the class without accessing any of its fields, then the test does not fail (undesired behavior).

package com.adevinta.archtests.application

import com.adevinta.archtests.infrastructure.controller.CarController

class CarUseCase {

    fun execute(carController: CarController) {
        println("CarUseCase: ${carController}")
    }
}

The CarController in this case is a dummy class like this:

package com.adevinta.archtests.infrastructure.controller

data class CarController(
    val name: String
)

However, if I run the following tests using 'layeredArchitecture' (see the following example), then the tests always fail for both cases in the example (desired result). I would like to know if the first test approach is incorrect because we have widely implemented the first solution in all our microservices in my company.

    @ArchTest
    val `layer dependencies between modules are respected` =
        Architectures.layeredArchitecture().consideringAllDependencies()
            .layer("application").definedBy("..application..")
            .layer("domain").definedBy("..domain..")
            .layer("infrastructure").definedBy("..infrastructure..")
            .whereLayer("application").mayOnlyBeAccessedByLayers("infrastructure")
            .whereLayer("domain").mayOnlyBeAccessedByLayers("infrastructure", "application")
            .whereLayer("infrastructure").mayNotBeAccessedByAnyLayer()            
@hankem
Copy link
Member

hankem commented May 29, 2024

ArchUnit's notion of "access" is more specific than of "dependencies", cf. documentation of accessClassesThat():

'access' refers only to violations by real accesses, i.e. accessing a field, and calling a method.
Compare with dependOnClassesThat() that catches a wider variety of violations.

So you could implement your HexagonalArchitectureTest with

@ArchTest
val `the domain classes should not access the application classes` =
    noClasses().that().resideInAPackage("..application..")
        .should().dependOnClassesThat().resideInAnyPackage("..infrastructure..")

I'll have to double check why the dependency

Method <..application.CarUseCase.execute(..infrastructure.controller.CarController)> has parameter of type <..infrastructure.controller.CarController> in (CarUseCase.kt:0)

is caught by whereLayer("infrastructure").mayNotBeAccessedByAnyLayer().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants