From b7c5a584a4228060263e9000b3d2ca696296d820 Mon Sep 17 00:00:00 2001 From: mnhock Date: Sun, 16 Jun 2024 19:12:17 +0200 Subject: [PATCH] Provide a new JUnit 5 Rule checking if the test methods do not declare any thrown exceptions Closes gh-30 --- docs/USERGUIDE.md | 53 ++++++++++++------- .../enofex/taikai/test/JUnit5Configurer.java | 44 ++++++++++++--- .../com/enofex/taikai/ArchitectureTest.java | 1 + src/test/java/com/enofex/taikai/Usage.java | 1 + 4 files changed, 73 insertions(+), 26 deletions(-) diff --git a/docs/USERGUIDE.md b/docs/USERGUIDE.md index 65c2561..f246afb 100644 --- a/docs/USERGUIDE.md +++ b/docs/USERGUIDE.md @@ -39,26 +39,27 @@ Architecture rules are defined using Taikai's fluent API, allowing developers to | | Naming | `fieldsAnnotatedWithShouldMatch` | Fields annotated with should match specific naming patterns | Default (WITHOUT_TESTS) | | | Naming | `constantsShouldFollowConvention` | Constants should follow naming conventions | Default (WITHOUT_TESTS) | | | Naming | `interfacesShouldNotHavePrefixI` | Interfaces should not have the prefix `I` | Default (WITHOUT_TESTS) | -| **Test** | JUnit 5 | `classesShouldNotBeAnnotatedWithDisabled` | Ensure classes are not annotated with `@Disabled` | Default (WITH_TESTS) | -| | JUnit 5 | `methodsShouldNotBeAnnotatedWithDisabled` | Ensure methods are not annotated with `@Disabled` | Default (WITH_TESTS) | -| | JUnit 5 | `methodsShouldBePackagePrivate` | Ensure that test methods annotated with `@Test` or `@ParameterizedTest` are package-private. | Default (WITH_TESTS) | -| | JUnit 5 | `methodsShouldBeAnnotatedWithDisplayName` | Ensure that test methods annotated with `@Test` or `@ParameterizedTest` are annotated with `@DisplayName`. | Default (WITH_TESTS) | -| | JUnit 5 | `methodsShouldMatch` | Ensure that test methods annotated with `@Test` or `@ParameterizedTest` have names matching a specific regex pattern. | Default (WITH_TESTS) | -| **Spring** | General | `noAutowiredFields` | Fields should not be annotated with `@Autowired` (prefer constructor injection) | Default (WITH_TESTS) | -| | Boot | `springBootApplicationShouldBeIn` | Ensure `@SpringBootApplication` is in the default package | Default (WITH_TESTS) | -| | Configurations | `namesShouldEndWithConfiguration` | Configuration classes should end with "Configuration" | Default (WITH_TESTS) | -| | Configurations | `namesShouldMatch` | Configuration classes should match a regex pattern | Default (WITH_TESTS) | -| | Controllers | `namesShouldEndWithController` | Controllers should end with "Controller" | Default (WITH_TESTS) | -| | Controllers | `namesShouldMatch` | Controllers should match a regex pattern | Default (WITH_TESTS) | -| | Controllers | `shouldBeAnnotatedWithRestController` | Controllers should be annotated with `@RestController` | Default (WITH_TESTS) | -| | Controllers | `shouldBePackagePrivate` | Controllers should be package-private | Default (WITH_TESTS) | -| | Controllers | `shouldNotDependOnOtherControllers` | Controllers should not depend on other controllers | Default (WITH_TESTS) | -| | Repositories | `namesShouldEndWithRepository` | Repositories should end with "Repository" | Default (WITH_TESTS) | -| | Repositories | `namesShouldMatch` | Repositories should match a regex pattern | Default (WITH_TESTS) | -| | Repositories | `shouldBeAnnotatedWithRepository` | Repositories should be annotated with `@Repository` | Default (WITH_TESTS) | -| | Services | `namesShouldEndWithService` | Services should end with "Service" | Default (WITH_TESTS) | -| | Services | `namesShouldMatch` | Services should match a regex pattern | Default (WITH_TESTS) | -| | Services | `shouldBeAnnotatedWithService` | Services should be annotated with `@Service` | Default (WITH_TESTS) | +| **Test** | JUnit 5 | `classesShouldNotBeAnnotatedWithDisabled` | Ensure classes are not annotated with `@Disabled` | Default (ONLY_TESTS) | +| | JUnit 5 | `methodsShouldNotBeAnnotatedWithDisabled` | Ensure methods are not annotated with `@Disabled` | Default (ONLY_TESTS) | +| | JUnit 5 | `methodsShouldBePackagePrivate` | Ensure that test methods annotated with `@Test` or `@ParameterizedTest` are package-private. | Default (ONLY_TESTS) | +| | JUnit 5 | `methodsShouldBeAnnotatedWithDisplayName` | Ensure that test methods annotated with `@Test` or `@ParameterizedTest` are annotated with `@DisplayName`. | Default (ONLY_TESTS) | +| | JUnit 5 | `methodsShouldMatch` | Ensure that test methods annotated with `@Test` or `@ParameterizedTest` have names matching a specific regex pattern. | Default (ONLY_TESTS) | +| | JUnit 5 | `methodsShouldNotDeclareThrownExceptions` | Ensure that test methods annotated with `@Test` or `@ParameterizedTest` do not declare any thrown exceptions. | Default (ONLY_TESTS) | +| **Spring** | General | `noAutowiredFields` | Fields should not be annotated with `@Autowired` (prefer constructor injection) | Default (WITHOUT_TESTS) | +| | Boot | `springBootApplicationShouldBeIn` | Ensure `@SpringBootApplication` is in the default package | Default (WITHOUT_TESTS) | +| | Configurations | `namesShouldEndWithConfiguration` | Configuration classes should end with "Configuration" | Default (WITHOUT_TESTS) | +| | Configurations | `namesShouldMatch` | Configuration classes should match a regex pattern | Default (WITHOUT_TESTS) | +| | Controllers | `namesShouldEndWithController` | Controllers should end with "Controller" | Default (WITHOUT_TESTS) | +| | Controllers | `namesShouldMatch` | Controllers should match a regex pattern | Default (WITHOUT_TESTS) | +| | Controllers | `shouldBeAnnotatedWithRestController` | Controllers should be annotated with `@RestController` | Default (WITHOUT_TESTS) | +| | Controllers | `shouldBePackagePrivate` | Controllers should be package-private | Default (WITHOUT_TESTS) | +| | Controllers | `shouldNotDependOnOtherControllers` | Controllers should not depend on other controllers | Default (WITHOUT_TESTS) | +| | Repositories | `namesShouldEndWithRepository` | Repositories should end with "Repository" | Default (WITHOUT_TESTS) | +| | Repositories | `namesShouldMatch` | Repositories should match a regex pattern | Default (WITHOUT_TESTS) | +| | Repositories | `shouldBeAnnotatedWithRepository` | Repositories should be annotated with `@Repository` | Default (WITHOUT_TESTS) | +| | Services | `namesShouldEndWithService` | Services should end with "Service" | Default (WITHOUT_TESTS) | +| | Services | `namesShouldMatch` | Services should match a regex pattern | Default (WITHOUT_TESTS) | +| | Services | `shouldBeAnnotatedWithService` | Services should be annotated with `@Service` | Default (WITHOUT_TESTS) | ### Java Configuration @@ -238,6 +239,18 @@ Taikai.builder() .check(); ``` +- **Ensure Test Methods Do Not Declare Thrown Exceptions** Ensure that JUnit 5 test methods annotated with `@Test` or `@ParameterizedTest` do not declare any thrown exceptions. + +```java +Taikai.builder() + .namespace("com.company.yourproject") + .test(test -> test + .junit5(junit5 -> junit5 + .methodsShouldNotDeclareThrownExceptions())) + .build() + .check(); +``` + ### Spring Configuration Spring configuration involves defining constraints specific to Spring Framework usage. diff --git a/src/main/java/com/enofex/taikai/test/JUnit5Configurer.java b/src/main/java/com/enofex/taikai/test/JUnit5Configurer.java index 56115c7..b0e907c 100644 --- a/src/main/java/com/enofex/taikai/test/JUnit5Configurer.java +++ b/src/main/java/com/enofex/taikai/test/JUnit5Configurer.java @@ -10,20 +10,26 @@ import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noMethods; -import com.enofex.taikai.Namespace; +import com.enofex.taikai.Namespace.IMPORT; import com.enofex.taikai.TaikaiRule; import com.enofex.taikai.TaikaiRule.Configuration; import com.enofex.taikai.configures.AbstractConfigurer; import com.enofex.taikai.configures.ConfigurerContext; +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 final class JUnit5Configurer extends AbstractConfigurer { + private static final Configuration CONFIGURATION = Configuration.of(IMPORT.ONLY_TESTS); + JUnit5Configurer(ConfigurerContext configurerContext) { super(configurerContext); } public JUnit5Configurer methodsShouldMatch(String regex) { - return methodsShouldMatch(regex, null); + return methodsShouldMatch(regex, CONFIGURATION); } public JUnit5Configurer methodsShouldMatch(String regex, Configuration configuration) { @@ -35,8 +41,21 @@ public JUnit5Configurer methodsShouldMatch(String regex, Configuration configura configuration)); } + public JUnit5Configurer methodsShouldNotDeclareThrownExceptions() { + return methodsShouldNotDeclareThrownExceptions(CONFIGURATION); + } + + public JUnit5Configurer methodsShouldNotDeclareThrownExceptions(Configuration configuration) { + return addRule(TaikaiRule.of(methods() + .that(are(annotatedWithTestOrParameterizedTest(true))) + .should(notDeclareThrownExceptions()) + .as("Methods annotated with %s or %s should not declare thrown Exceptions".formatted( + ANNOTATION_TEST, ANNOTATION_PARAMETRIZED_TEST)), + configuration)); + } + public JUnit5Configurer methodsShouldBeAnnotatedWithDisplayName() { - return methodsShouldBeAnnotatedWithDisplayName(Configuration.of(Namespace.IMPORT.WITH_TESTS)); + return methodsShouldBeAnnotatedWithDisplayName(CONFIGURATION); } public JUnit5Configurer methodsShouldBeAnnotatedWithDisplayName(Configuration configuration) { @@ -49,7 +68,7 @@ public JUnit5Configurer methodsShouldBeAnnotatedWithDisplayName(Configuration co } public JUnit5Configurer methodsShouldBePackagePrivate() { - return methodsShouldBePackagePrivate(Configuration.of(Namespace.IMPORT.WITH_TESTS)); + return methodsShouldBePackagePrivate(CONFIGURATION); } public JUnit5Configurer methodsShouldBePackagePrivate(Configuration configuration) { @@ -62,7 +81,7 @@ public JUnit5Configurer methodsShouldBePackagePrivate(Configuration configuratio } public JUnit5Configurer methodsShouldNotBeAnnotatedWithDisabled() { - return methodsShouldNotBeAnnotatedWithDisabled(Configuration.of(Namespace.IMPORT.WITH_TESTS)); + return methodsShouldNotBeAnnotatedWithDisabled(CONFIGURATION); } public JUnit5Configurer methodsShouldNotBeAnnotatedWithDisabled(Configuration configuration) { @@ -73,7 +92,7 @@ public JUnit5Configurer methodsShouldNotBeAnnotatedWithDisabled(Configuration co } public JUnit5Configurer classesShouldNotBeAnnotatedWithDisabled() { - return classesShouldNotBeAnnotatedWithDisabled(Configuration.of(Namespace.IMPORT.WITH_TESTS)); + return classesShouldNotBeAnnotatedWithDisabled(CONFIGURATION); } public JUnit5Configurer classesShouldNotBeAnnotatedWithDisabled(Configuration configuration) { @@ -82,4 +101,17 @@ public JUnit5Configurer classesShouldNotBeAnnotatedWithDisabled(Configuration co .as("Classes should not be annotated with %s".formatted(ANNOTATION_DISABLED)), configuration)); } + + private ArchCondition notDeclareThrownExceptions() { + return new ArchCondition<>("not declare thrown exceptions") { + @Override + public void check(JavaMethod method, ConditionEvents events) { + if (!method.getThrowsClause().isEmpty()) { + String message = String.format("Method %s declares thrown exceptions", + method.getFullName()); + events.add(SimpleConditionEvent.violated(method, message)); + } + } + }; + } } \ No newline at end of file diff --git a/src/test/java/com/enofex/taikai/ArchitectureTest.java b/src/test/java/com/enofex/taikai/ArchitectureTest.java index d5f49ba..cc80b73 100644 --- a/src/test/java/com/enofex/taikai/ArchitectureTest.java +++ b/src/test/java/com/enofex/taikai/ArchitectureTest.java @@ -16,6 +16,7 @@ void shouldFulfilConstrains() { .namespace("com.enofex.taikai") .test(test -> test .junit5(junit5 -> junit5 + .methodsShouldNotDeclareThrownExceptions() .methodsShouldMatch("should.*") .methodsShouldBePackagePrivate() .classesShouldNotBeAnnotatedWithDisabled() diff --git a/src/test/java/com/enofex/taikai/Usage.java b/src/test/java/com/enofex/taikai/Usage.java index 1ecd491..7622b26 100644 --- a/src/test/java/com/enofex/taikai/Usage.java +++ b/src/test/java/com/enofex/taikai/Usage.java @@ -31,6 +31,7 @@ public static void main(String[] args) { .namesShouldEndWithRepository())) .test(test -> test .junit5(junit5 -> junit5 + .methodsShouldNotDeclareThrownExceptions() .methodsShouldMatch("should.*") .methodsShouldBePackagePrivate() .methodsShouldBeAnnotatedWithDisplayName()