Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6ac0961
added archunit and fixed tests
DanielOber Sep 12, 2025
8bcf652
spotless
DanielOber Sep 15, 2025
41d5b5c
code rabbit review
DanielOber Sep 15, 2025
6ba6a2b
code rabbit review
DanielOber Sep 15, 2025
429c843
Merge branch 'main' into 758-feature-archunit---validate-test-naming-…
DanielOber Sep 17, 2025
2a308f4
Merge branch 'main' into 758-feature-archunit---validate-test-naming-…
DanielOber Sep 18, 2025
564bca9
renaming
DanielOber Sep 18, 2025
8988021
Merge remote-tracking branch 'origin/758-feature-archunit---validate-…
DanielOber Sep 18, 2025
8cc94f3
coderabbit review eingearbeitet
DanielOber Oct 10, 2025
8910444
Merge branch 'main' into 758-feature-archunit---validate-test-naming-…
DanielOber Nov 4, 2025
a5fb268
Merge branch 'main' into 758-feature-archunit---validate-test-naming-…
devtobi Nov 19, 2025
4c5f042
moved rule stream to the top
DanielOber Mar 11, 2026
0ae4fbd
Merge remote-tracking branch 'origin/758-feature-archunit---validate-…
DanielOber Mar 11, 2026
673916e
better naming for archunit tests
DanielOber Mar 11, 2026
16de0a0
Merge branch 'main' into 758-feature-archunit---validate-test-naming-…
DanielOber Mar 11, 2026
10f211f
removed empty line
DanielOber Mar 11, 2026
f3f2106
changed path to class
DanielOber Mar 11, 2026
d98fc05
spottless
DanielOber Mar 11, 2026
8b9f6c6
Update refarch-backend/src/test/java/de/muenchen/oss/refarch/backend/…
DanielOber Mar 12, 2026
bd977a2
Update refarch-backend/src/test/java/de/muenchen/oss/refarch/backend/…
DanielOber Mar 12, 2026
8232389
review feedback eingearbeitet
DanielOber Mar 12, 2026
686b24f
Merge remote-tracking branch 'origin/758-feature-archunit---validate-…
DanielOber Mar 12, 2026
6da281d
spottless
DanielOber Mar 12, 2026
4083b22
Update refarch-backend/src/test/java/de/muenchen/refarch/archunit/rul…
DanielOber Mar 12, 2026
f8ea154
Update refarch-backend/src/test/java/de/muenchen/refarch/archunit/rul…
DanielOber Mar 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions refarch-backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<!-- Testing -->
<jacoco-maven-plugin.version>0.8.14</jacoco-maven-plugin.version>
<argLine /> <!-- Must be empty, definition needed for integration of Jacoco and Surefire via @{argLine} lazy property evaluation -->
<archunit.version>1.4.1</archunit.version>

<!-- Release -->
<maven-release-plugin.version>3.3.1</maven-release-plugin.version>
Expand Down Expand Up @@ -259,6 +260,12 @@
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit</artifactId>
<version>${archunit.version}</version>
<scope>test</scope>
</dependency>

</dependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class CacheControlFilterTest {
private TestRestTemplate testRestTemplate;

@Test
void testForCacheControlHeadersForEntityEndpoint() {
void givenEntityEndpoint_thenCacheControlHeadersPresent() {
final ResponseEntity<String> response = testRestTemplate.exchange(ENTITY_ENDPOINT_URL, HttpMethod.GET, null, String.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertTrue(response.getHeaders().containsKey(HttpHeaders.CACHE_CONTROL));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class UnicodeFilterConfigurationTest {
private TheEntityRepository theEntityRepository;

@Test
void testForNfcNormalization() {
void givenDecomposedString_thenCovertToNfcNormalized() {
// Given
// Persist entity with decomposed string.
final TheEntityRequestDTO theEntityRequestDto = new TheEntityRequestDTO(TEXT_ATTRIBUTE_DECOMPOSED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class NfcConverterTest {

// Test that request with configured ContentType is normalized to NFC.
@Test
void testFilterIfContenttypeInWhitelist() throws ServletException, IOException {
void givenContentTypeInWhitelist_thenFilter() throws ServletException, IOException {
mockRequest("text/plain");

filter.doFilter(req, resp, chain);
Expand All @@ -90,7 +90,7 @@ void testFilterIfContenttypeInWhitelist() throws ServletException, IOException {

// Test that Request not configured ContentType remains unchanged, i.e. is not normalized to NFC.
@Test
void testSkipFilterIfContenttypeNotInWhitelist() throws ServletException, IOException {
void givenContentTypeNotInWhitelist_thenSkipFilter() throws ServletException, IOException {
mockRequest("application/notvalid");

filter.doFilter(req, resp, chain);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class NfcHelperTest {
private static final String[] NFC_OUTPUT_EXPECTED = { FIRST_NFC, SECOND_NFC, THIRD_NFC };

@Test
void nfcConverterString() {
void givenString_thenNfcConverted() {
assertEquals(FIRST_NFC, NfcHelper.nfcConverter(FIRST_NFD));
assertEquals(FIRST_NFC.length(), NfcHelper.nfcConverter(FIRST_NFD).length());

Expand All @@ -41,7 +41,7 @@ void nfcConverterString() {
}

@Test
void nfcConverterStringBuffer() {
void givenStringBuffer_thenNfcConverted() {
assertEquals(FIRST_NFC, NfcHelper.nfcConverter(new StringBuffer(FIRST_NFD)).toString());
assertEquals(FIRST_NFC.length(), NfcHelper.nfcConverter(new StringBuffer(FIRST_NFD)).length());

Expand All @@ -53,13 +53,13 @@ void nfcConverterStringBuffer() {
}

@Test
void nfcConverterStringArray() {
void givenStringArray_thenNfcConverted() {
assertArrayEquals(NFC_OUTPUT_EXPECTED, NfcHelper.nfcConverter(NFD_INPUT));
assertEquals(NFC_OUTPUT_EXPECTED.length, NfcHelper.nfcConverter(NFD_INPUT).length);
}

@Test
void nfcConverterMapOfStrings() {
void givenMapOfStrings_thenNfcConverted() {
final Map<String, String[]> nfdInput = new HashMap<>();
nfdInput.put(FIRST_NFD, NFD_INPUT);
nfdInput.put(SECOND_NFD, NFD_INPUT);
Expand All @@ -73,7 +73,7 @@ void nfcConverterMapOfStrings() {
}

@Test
void nfcConverterCookie() {
void givenCookie_thenNfcConverted() {
final Cookie nfcCookie = NfcHelper.nfcConverter(createNfdCookie());

assertEquals(NfcConverterTest.TOKEN, nfcCookie.getName());
Expand All @@ -84,7 +84,7 @@ void nfcConverterCookie() {

@SuppressWarnings("PMD.UnitTestShouldIncludeAssert")
@Test
void nfcConverterCookieArray() {
void givenCookieArray_thenNfcConverted() {
final Cookie[] nfdCookies = Collections.nCopies(3, createNfdCookie()).toArray(new Cookie[3]);
final Cookie[] nfcCookies = NfcHelper.nfcConverter(nfdCookies);
Arrays.asList(nfcCookies).forEach(nfcCookie -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ void setUp() {
}

@Test
void testConvert_WithAuthorities() {
void givenNoRoles_thenConvert() {
// Setup
final Jwt jwt = mock(Jwt.class);
when(jwt.getSubject()).thenReturn(TEST_SUBJECT);
Expand All @@ -86,7 +86,7 @@ void testConvert_WithAuthorities() {
}

@Test
void testConvert_NoAuthorities() {
void givenNoAuthorities_thenConvert() {
// Setup
final Jwt jwt = mock(Jwt.class);
when(jwt.getSubject()).thenReturn(TEST_SUBJECT);
Expand All @@ -105,7 +105,7 @@ void testConvert_NoAuthorities() {
}

@Test
void testConvert_CacheHit() {
void givenCacheHit_thenConvert() {
// Setup
final Jwt jwt = mock(Jwt.class);
when(jwt.getSubject()).thenReturn(TEST_SUBJECT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ void setUp() {
}

@Test
void testConvert_WithRoles() {
void givenRoles_thenConvert() {
// Setup
final Map<String, Object> resourceAccessClaim = new HashMap<>();
resourceAccessClaim.put(TEST_CLIENT, Map.of("roles", List.of("admin", "user")));
Expand All @@ -54,7 +54,7 @@ void testConvert_WithRoles() {
}

@Test
void testConvert_WithoutRoles() {
void givenNoRoles_thenConvert() {
// Setup
final Map<String, Object> claims = new HashMap<>();
claims.put(RESOURCE_ACCESS_CLAIM, Map.of(
Expand All @@ -71,7 +71,7 @@ void testConvert_WithoutRoles() {
}

@Test
void testConvert_ClientNotInResourceAccess() {
void givenClientNotInResourceAccess_thenConvert() {
// Setup
final Map<String, Object> resourceAccessClaim = new HashMap<>();
resourceAccessClaim.put("other-client", Map.of("roles", List.of("admin")));
Expand All @@ -87,7 +87,7 @@ void testConvert_ClientNotInResourceAccess() {
}

@Test
void testConvert_NullClaims() {
void givenNullClaims_thenConvert() {
// Setup
final Jwt jwt = mock(Jwt.class);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package de.muenchen.refarch.archunit;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.lang.ArchRule;
import de.muenchen.oss.refarch.backend.MicroServiceApplication;
import de.muenchen.refarch.archunit.rules.Rules;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.aggregator.ArgumentsAccessor;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

public class ArchUnitTest {

private static JavaClasses allTestClasses;

@BeforeAll
static void init() {
allTestClasses = new ClassFileImporter()
.withImportOption(new ImportOption.OnlyIncludeTests())
.importPackages(MicroServiceApplication.class.getPackage().getName());
}

public static Stream<Arguments> allTestClassesRulesToVerify() {
return Stream.of(
Arguments.of("Test classes must end with 'Test'",
Rules.RULE_TESTCLASSES_END_WITH_TEST_CONVENTION_MATCHED),
Arguments.of("Test methods must follow 'given_then' naming convention",
Rules.RULE_TEST_NAMING_CONVENTION_GIVEN_THEN_MATCHED),
Arguments.of("BeforeEach methods must follow naming convention",
Rules.RULE_BEFORE_EACH_NAMING_CONVENTION_MATCHED),
Arguments.of("AfterEach methods must follow naming convention",
Rules.RULE_AFTER_EACH_NAMING_CONVENTION_MATCHED),
Arguments.of("Test methods should be package-private",
Rules.RULE_TEST_METHODS_ARE_PACKAGE_PRIVATE_CONVENTION_MATCHED));
}

@ParameterizedTest(name = "{0}")
@MethodSource("allTestClassesRulesToVerify")
void givenAllArchUnitRulesForAllTestClasses_thenRunArchUnitTests(final ArgumentsAccessor arguments) {
arguments.get(1, ArchRule.class).check(allTestClasses);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package de.muenchen.refarch.archunit.rules;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
import static de.muenchen.refarch.archunit.rules.TestClassesEndWithTestCondition.haveTopEnclosingClassEndingWithTest;

import com.tngtech.archunit.core.domain.JavaModifier;
import com.tngtech.archunit.lang.ArchRule;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.params.ParameterizedTest;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class Rules {

public static final ArchRule RULE_TEST_NAMING_CONVENTION_GIVEN_THEN_MATCHED = methods()
.that().areAnnotatedWith(Test.class)
.or().areAnnotatedWith(ParameterizedTest.class)
.or().areAnnotatedWith(RepeatedTest.class)
.or().areAnnotatedWith(TestTemplate.class)
.should().haveNameMatching("^given[A-Z][a-zA-Z]+_then[A-Z][a-zA-Z]+$");

public static final ArchRule RULE_BEFORE_EACH_NAMING_CONVENTION_MATCHED = methods()
.that().areAnnotatedWith(BeforeEach.class)
.should().haveNameMatching("^setUp$")
.allowEmptyShould(false);

public static final ArchRule RULE_AFTER_EACH_NAMING_CONVENTION_MATCHED = methods()
.that().areAnnotatedWith(AfterEach.class)
.should().haveNameMatching("^tearDown$")
.allowEmptyShould(false);

public static final ArchRule RULE_TEST_METHODS_ARE_PACKAGE_PRIVATE_CONVENTION_MATCHED = methods()
.that().areAnnotatedWith(Test.class)
.or().areAnnotatedWith(ParameterizedTest.class)
.or().areAnnotatedWith(RepeatedTest.class)
.or().areAnnotatedWith(TestTemplate.class)
.should().notHaveModifier(JavaModifier.PROTECTED)
.andShould().notHaveModifier(JavaModifier.PRIVATE)
.andShould().notHaveModifier(JavaModifier.PUBLIC);

public static final ArchRule RULE_TESTCLASSES_END_WITH_TEST_CONVENTION_MATCHED = methods()
.that().areAnnotatedWith(Test.class)
.or().areAnnotatedWith(ParameterizedTest.class)
.or().areAnnotatedWith(RepeatedTest.class)
.or().areAnnotatedWith(TestTemplate.class)
.should(haveTopEnclosingClassEndingWithTest);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package de.muenchen.refarch.archunit.rules;

import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaMethod;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;

public class TestClassesEndWithTestCondition extends ArchCondition<JavaMethod> {

TestClassesEndWithTestCondition() {
super("have top enclosing class name ending with `Test`");
}

public static final ArchCondition<JavaMethod> haveTopEnclosingClassEndingWithTest = new TestClassesEndWithTestCondition();

@Override
public void check(JavaMethod method, ConditionEvents events) {
final var topEnclosingClass = getTopEnclosingClass(method.getOwner());

if (!topEnclosingClass.getSimpleName().endsWith("Test")) {
events.add(SimpleConditionEvent.violated(method, "Method %s must be declared in a class whose simple name ends with 'Test' (found: %s)"
.formatted(method.getName(), topEnclosingClass.getSimpleName())));
}
}

private JavaClass getTopEnclosingClass(JavaClass item) {
while (item.getEnclosingClass().isPresent()) {
item = item.getEnclosingClass().orElseThrow();
}
return item;
}
}
Loading