TNG / ArchUnit

A Java architecture test library, to specify and assert architecture rules in plain Java
http://archunit.org
Apache License 2.0
3.23k stars 298 forks source link

Naming convention test of a class containing lambda with switch and enum #1019

Open tomek-w-k opened 1 year ago

tomek-w-k commented 1 year ago

In a Spring Boot 2.7 project I implemented the following code:

package com.app.archunittest.constant;

public enum DummyEnum {
    FIRST,
    SECOND,
    THIRD
}
package com.app.archunittest.service;
import com.app.archunittest.constant.DummyEnum;
import org.springframework.stereotype.Service;
import java.util.Optional;

@Service
public class DummyService {

    private DummyEnum dummyEnum = DummyEnum.FIRST;

    public void dummyMethod() {
        Optional.ofNullable(dummyEnum)
                .ifPresent(de -> {
                    switch(de) {
                        case FIRST:
                            System.out.println("First item selected.");
                            break;
                        case SECOND:
                            System.out.println("Second item selected.");
                            break;
                        case THIRD:
                            System.out.println("Third item selected.");
                            break;
                    }
                });
    }
}

Then, with ArchUnit 1.0.1 I created a test for it:

import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;

@AnalyzeClasses(packages = "com.app.archunittest")
public class NamingConventionTest {

    @ArchTest
    static ArchRule serviceShouldBeSuffixed = classes()
            .that()
            .resideInAPackage("..service..")
            .should()
            .haveSimpleNameEndingWith("Service");
}

The test fails with an error:

java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'classes that reside in a package '..service..' should have simple name ending with 'Service'' was violated (1 times): Class <com.app.archunittest.service.DummyService$1> does not have simple name ending with 'Service' in (DummyService.java:0)

Then I created a custom annotation @IgnoreArchUnitTest:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface IgnoreArchUnitTest {
}

and applied it to my service class. Modifiled the test:

@ArchTest
static ArchRule serviceShouldBeSuffixed = classes()
        .that()
        .resideInAPackage("..service..")
        .and()
        .areNotAnnotatedWith(IgnoreArchUnitTest.class)
        .should()
        .haveSimpleNameEndingWith("Service");

Service class should now be ignored by the test, but the error message I got was almost the same:

java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'classes that reside in a package '..service..' and are not annotated with @IgnoreArchUnitTest should have simple name ending with 'Service'' was violated (1 times): Class <com.app.archunittest.service.DummyService$1> does not have simple name ending with 'Service' in (DummyService.java:0)

Ok, the error message rightly says that the Class <com.app.archunittest.service.DummyService$1> does not have simple name ending with 'Service'. Thus, I removed the @IgnoreArchUnitTest annotation both from the service class and from the test. Added a new condition to the test for checking the "Service$1" suffix:

@ArchTest
static ArchRule serviceShouldBeSuffixed = classes()
        .that()
        .resideInAPackage("..service..")
        .should()
        .haveSimpleNameEndingWith("Service")
        .orShould()
        .haveSimpleNameEndingWith("Service$1");

Now, the error says:

java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'classes that reside in a package '..service..' should have simple name ending with 'Service' or should have simple name ending with 'Service$1'' was violated (1 times): Class <com.app.archunittest.service.DummyService$1> does not have simple name ending with 'Service$1' in (DummyService.java:0) and Class <com.app.archunittest.service.DummyService$1> does not have simple name ending with 'Service' in (DummyService.java:0)

"Class <com.app.archunittest.service.DummyService$1> does not have simple name ending with 'Service$1'" That's interesting...

However, when I modified my service method as below:

public void dummyMethod() {
    Optional.ofNullable(dummyEnum)
            .ifPresent(de -> {
                switch(de.name()) {
                    case "FIRST":
                        System.out.println("First item selected.");
                        break;
                    case "SECOND":
                        System.out.println("Second item selected.");
                        break;
                    case "THIRD":
                        System.out.println("Third item selected.");
                        break;
                }
            });
}

by replacing enum values with corresponding strings within the switch statement, the test passed successfully.

Is that a correct behavior of that test or maybe I did something wrong?

hankem commented 1 year ago

The synthetic class com.app.archunittest.service.DummyService$1 doesn't have a simple name (but ""). Technically, you could use orShould().haveNameMatching(".*\\.Service\\$1"), but you probably want to use the solution classes().that().doNotHaveModifier(JavaModifier.SYNTHETIC) from https://github.com/TNG/ArchUnit/issues/1011#issuecomment-1336332226.

saugion commented 8 months ago

SYNTHETIC doesn't work for me, but i've noticed that these generated classes are recognised as anonymous. As a workaround i use .areNotAnonymousClasses() to exclude them from the evaluation