Skip to content

Commit

Permalink
Provide a new JUnit 5 Rule checking test methods are package private
Browse files Browse the repository at this point in the history
Closes gh-27
  • Loading branch information
mnhock committed Jun 16, 2024
1 parent d706e85 commit bed3279
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 48 deletions.
87 changes: 50 additions & 37 deletions docs/USERGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,43 +19,44 @@ Architecture rules are defined using Taikai's fluent API, allowing developers to

## 3. Usage

| Category | Subcategory | Method Name | Rule Description | Import Options |
|------------|----------------|--------------------------------------------------------|---------------------------------------------------------------------------------|-------------------------|
| **Java** | General | `classesShouldImplementHashCodeAndEquals` | Classes should implement `hashCode` and `equals` | Default (WITHOUT_TESTS) |
| | General | `fieldsShouldNotBePublic` | Fields should not be `public` (except constants) | Default (WITHOUT_TESTS) |
| | General | `methodsShouldNotThrowGenericException` | Methods should not throw generic exceptions (`Exception`, `RuntimeException`) | Default (WITHOUT_TESTS) |
| | General | `noUsageOf` | Disallow usage of specific classes | Default (WITHOUT_TESTS) |
| | General | `noUsageOf` | Disallow usage of specific classes by class reference | Default (WITHOUT_TESTS) |
| | General | `noUsageOfDeprecatedAPIs` | No usage of deprecated APIs annotated with `Deprecated` | Default (WITHOUT_TESTS) |
| | General | `noUsageOfSystemOutOrErr` | Disallow usage of `System.out` or `System.err` | Default (WITHOUT_TESTS) |
| | General | `utilityClassesShouldBeFinalAndHavePrivateConstructor` | Utility classes should be `final` and have a private constructor | Default (WITHOUT_TESTS) |
| | Imports | `shouldHaveNoCycles` | No cyclic dependencies in imports | Default (WITHOUT_TESTS) |
| | Imports | `shouldNotImport` | Disallow specific imports (e.g., `..shaded..`) | Default (WITHOUT_TESTS) |
| | Naming | `classesShouldNotMatch` | Classes should not match specific naming patterns (e.g., `.*Impl`) | Default (WITHOUT_TESTS) |
| | Naming | `methodsShouldNotMatch` | Methods should not match specific naming patterns | Default (WITHOUT_TESTS) |
| | Naming | `fieldsShouldNotMatch` | Fields should not match specific naming patterns | Default (WITHOUT_TESTS) |
| | Naming | `classesAnnotatedWithShouldMatch` | Classes annotated with should match specific naming patterns | Default (WITHOUT_TESTS) |
| | Naming | `methodsAnnotatedWithShouldMatch` | Methods annotated with should match specific naming patterns | Default (WITHOUT_TESTS) |
| | 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 | `jclassesShouldNotBeAnnotatedWithDisabled` | Ensure JUnit 5 classes are not annotated with `@Disabled` | Default (WITH_TESTS) |
| | JUnit 5 | `jmethodsShouldNotBeAnnotatedWithDisabled` | Ensure JUnit 5 methods are not annotated with `@Disabled` | 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) |
| Category | Subcategory | Method Name | Rule Description | Import Options |
|------------|----------------|--------------------------------------------------------|----------------------------------------------------------------------------------------------|-------------------------|
| **Java** | General | `classesShouldImplementHashCodeAndEquals` | Classes should implement `hashCode` and `equals` | Default (WITHOUT_TESTS) |
| | General | `fieldsShouldNotBePublic` | Fields should not be `public` (except constants) | Default (WITHOUT_TESTS) |
| | General | `methodsShouldNotThrowGenericException` | Methods should not throw generic exceptions (`Exception`, `RuntimeException`) | Default (WITHOUT_TESTS) |
| | General | `noUsageOf` | Disallow usage of specific classes | Default (WITHOUT_TESTS) |
| | General | `noUsageOf` | Disallow usage of specific classes by class reference | Default (WITHOUT_TESTS) |
| | General | `noUsageOfDeprecatedAPIs` | No usage of deprecated APIs annotated with `Deprecated` | Default (WITHOUT_TESTS) |
| | General | `noUsageOfSystemOutOrErr` | Disallow usage of `System.out` or `System.err` | Default (WITHOUT_TESTS) |
| | General | `utilityClassesShouldBeFinalAndHavePrivateConstructor` | Utility classes should be `final` and have a private constructor | Default (WITHOUT_TESTS) |
| | Imports | `shouldHaveNoCycles` | No cyclic dependencies in imports | Default (WITHOUT_TESTS) |
| | Imports | `shouldNotImport` | Disallow specific imports (e.g., `..shaded..`) | Default (WITHOUT_TESTS) |
| | Naming | `classesShouldNotMatch` | Classes should not match specific naming patterns (e.g., `.*Impl`) | Default (WITHOUT_TESTS) |
| | Naming | `methodsShouldNotMatch` | Methods should not match specific naming patterns | Default (WITHOUT_TESTS) |
| | Naming | `fieldsShouldNotMatch` | Fields should not match specific naming patterns | Default (WITHOUT_TESTS) |
| | Naming | `classesAnnotatedWithShouldMatch` | Classes annotated with should match specific naming patterns | Default (WITHOUT_TESTS) |
| | Naming | `methodsAnnotatedWithShouldMatch` | Methods annotated with should match specific naming patterns | Default (WITHOUT_TESTS) |
| | 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) |
| **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) |

### Java Configuration

Expand Down Expand Up @@ -199,6 +200,18 @@ Taikai.builder()
.check();
```

- **Ensure Test Methods are Package-Private**: Ensure that JUnit 5 test methods annotated with `@Test` or `@ParameterizedTest` are package-private.

```java
Taikai.builder()
.namespace("com.company.yourproject")
.test(test -> test
.junit5(junit5 -> junit5
.methodsShouldBePackagePrivate()))
.build()
.check();
```

### Spring Configuration

Spring configuration involves defining constraints specific to Spring Framework usage.
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/com/enofex/taikai/AnnotationPredicates.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.enofex.taikai;

import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.properties.CanBeAnnotated;

public final class AnnotationPredicates {

private AnnotationPredicates() {
}

public static DescribedPredicate<CanBeAnnotated> annotatedWith(String annotation,
boolean isMetaAnnotated) {
return new DescribedPredicate<>("annotated with %s".formatted(annotation)) {
@Override
public boolean test(CanBeAnnotated canBeAnnotated) {
return isMetaAnnotated ? canBeAnnotated.isMetaAnnotatedWith(annotation) :
canBeAnnotated.isAnnotatedWith(annotation);
}
};
}
}
13 changes: 2 additions & 11 deletions src/main/java/com/enofex/taikai/spring/SpringPredicates.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.enofex.taikai.spring;

import static com.enofex.taikai.AnnotationPredicates.annotatedWith;

import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.properties.CanBeAnnotated;

Expand Down Expand Up @@ -52,15 +54,4 @@ static DescribedPredicate<CanBeAnnotated> annotatedWithSpringBootApplication(
static DescribedPredicate<CanBeAnnotated> annotatedAutowired(boolean isMetaAnnotated) {
return annotatedWith(ANNOTATION_AUTOWIRED, isMetaAnnotated);
}

private static DescribedPredicate<CanBeAnnotated> annotatedWith(String annotation,
boolean isMetaAnnotated) {
return new DescribedPredicate<>("annotated with %s".formatted(annotation)) {
@Override
public boolean test(CanBeAnnotated canBeAnnotated) {
return isMetaAnnotated ? canBeAnnotated.isMetaAnnotatedWith(annotation) :
canBeAnnotated.isAnnotatedWith(annotation);
}
};
}
}
18 changes: 18 additions & 0 deletions src/main/java/com/enofex/taikai/test/JUnit5Configurer.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package com.enofex.taikai.test;

import static com.enofex.taikai.test.JUnit5Predicates.ANNOTATION_PARAMETRIZED_TEST;
import static com.enofex.taikai.test.JUnit5Predicates.ANNOTATION_TEST;
import static com.enofex.taikai.test.JUnit5Predicates.annotatedWithTestOrParameterizedTest;
import static com.tngtech.archunit.lang.conditions.ArchPredicates.are;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noMethods;

Expand All @@ -17,6 +22,19 @@ public final class JUnit5Configurer extends AbstractConfigurer {
super(configurerContext);
}

public JUnit5Configurer methodsShouldBePackagePrivate() {
return methodsShouldBePackagePrivate(Configuration.of(Namespace.IMPORT.WITH_TESTS));
}

public JUnit5Configurer methodsShouldBePackagePrivate(Configuration configuration) {
return addRule(TaikaiRule.of(methods()
.that(are(annotatedWithTestOrParameterizedTest(true)))
.should().bePackagePrivate()
.as("Methods annotated with %s or %s should be package-private".formatted(ANNOTATION_TEST,
ANNOTATION_PARAMETRIZED_TEST)),
configuration));
}

public JUnit5Configurer methodsShouldNotBeAnnotatedWithDisabled() {
return methodsShouldNotBeAnnotatedWithDisabled(Configuration.of(Namespace.IMPORT.WITH_TESTS));
}
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/com/enofex/taikai/test/JUnit5Predicates.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.enofex.taikai.test;

import static com.enofex.taikai.AnnotationPredicates.annotatedWith;

import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.properties.CanBeAnnotated;

final class JUnit5Predicates {

static final String ANNOTATION_TEST = "org.junit.jupiter.api.Test";
static final String ANNOTATION_PARAMETRIZED_TEST = "org.junit.jupiter.params.ParameterizedTest";

private JUnit5Predicates() {
}

static DescribedPredicate<CanBeAnnotated> annotatedWithTestOrParameterizedTest(
boolean isMetaAnnotated) {

return annotatedWith(ANNOTATION_TEST, isMetaAnnotated)
.or(annotatedWith(ANNOTATION_PARAMETRIZED_TEST, isMetaAnnotated));
}
}
1 change: 1 addition & 0 deletions src/test/java/com/enofex/taikai/ArchitectureTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ void shouldFulfilConstrains() {
.namespace("com.enofex.taikai")
.test(test -> test
.junit5(junit5 -> junit5
.methodsShouldBePackagePrivate()
.classesShouldNotBeAnnotatedWithDisabled()
.methodsShouldNotBeAnnotatedWithDisabled()))
.java(java -> java
Expand Down
Loading

0 comments on commit bed3279

Please sign in to comment.