Skip to content
Open
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.2.0</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
@@ -0,0 +1,42 @@
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.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(de.muenchen.refarch.MicroServiceApplication.class.getPackage().getName());
}

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

public static Stream<Arguments> allTestClassesRulesToVerify() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd move this method all the way to the top because when we add a new rule this stream needs to be extended but the parameterized itself not so its less important.

return Stream.of(
Arguments.of("RULE_TESTCLASSES_END_WITH_TEST_CONVENTION_MATCHED",
Rules.RULE_TESTCLASSES_END_WITH_TEST_CONVENTION_MATCHED),
Arguments.of("TEST_NAMING_CONVENTION_RULE", Rules.RULE_TEST_NAMING_CONVENTION_GIVEN_THEN_MATCHED),
Arguments.of("RULE_BEFORE_EACH_NAMING_CONVENTION_MATCHED", Rules.RULE_BEFORE_EACH_NAMING_CONVENTION_MATCHED),
Arguments.of("RULE_AFTER_EACH_NAMING_CONVENTION_MATCHED", Rules.RULE_AFTER_EACH_NAMING_CONVENTION_MATCHED),
Arguments.of("TEST_METHODS_ARE_PACKAGE_PRIVATE_CONVENTION_MATCHED", Rules.RULE_TEST_METHODS_ARE_PACKAGE_PRIVATE_CONVENTION_MATCHED));
Comment on lines +34 to +39
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of duplicating the rule name I'd add a more descriptive name that describes the ArchUnit check

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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)
Comment on lines +21 to +25
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most tests have these 4 lines in common, maybe you can move them into a reusable snippet? e.g. areTests()

.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(true);

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

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()
Comment on lines +37 to +40
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code duplication

.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)
Comment on lines +46 to +49
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code duplication

.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;
}
}
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 @@ -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
Expand Up @@ -48,7 +48,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 @@ -70,7 +70,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 @@ -89,7 +89,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
Loading