Skip to content

Commit

Permalink
Add JUnit 5 Rule to Ensure Test Methods Contain Assertions or Verific…
Browse files Browse the repository at this point in the history
…ations

Closes gh-99
  • Loading branch information
mnhock authored and mnhock committed Sep 14, 2024
1 parent 3bc5c1f commit c66875e
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 23 deletions.
41 changes: 32 additions & 9 deletions docs/USERGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,16 @@ The default mode is `WITHOUT_TESTS`, which checks only test classes.

The default mode is `ONLY_TESTS`, which checks only test classes.

| Category | Method Name | Rule Description |
|---------------|--------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|
| JUnit 5 | `classesShouldBePackagePrivate` | Ensure that classes whose names match a specific naming pattern are declared as package-private. |
| JUnit 5 | `classesShouldNotBeAnnotatedWithDisabled` | Ensure classes are not annotated with `@Disabled`. |
| JUnit 5 | `methodsShouldBePackagePrivate` | Ensure that test methods annotated with `@Test` or `@ParameterizedTest` are package-private. |
| JUnit 5 | `methodsShouldNotBeAnnotatedWithDisabled` | Ensure methods are not annotated with `@Disabled`. |
| JUnit 5 | `methodsShouldBeAnnotatedWithDisplayName` | Ensure that test methods annotated with `@Test` or `@ParameterizedTest` are annotated with `@DisplayName`. |
| JUnit 5 | `methodsShouldMatch` | Ensure that test methods annotated with `@Test` or `@ParameterizedTest` have names matching a specific regex pattern. |
| JUnit 5 | `methodsShouldNotDeclareExceptions` | Ensure that test methods annotated with `@Test` or `@ParameterizedTest` do not declare any thrown exceptions. |
| Category | Method Name | Rule Description |
|----------|--------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------|
| JUnit 5 | `classesShouldBePackagePrivate` | Ensure that classes whose names match a specific naming pattern are declared as package-private. |
| JUnit 5 | `classesShouldNotBeAnnotatedWithDisabled` | Ensure classes are not annotated with `@Disabled`. |
| JUnit 5 | `methodsShouldBePackagePrivate` | Ensure that test methods annotated with `@Test` or `@ParameterizedTest` are package-private. |
| JUnit 5 | `methodsShouldNotBeAnnotatedWithDisabled` | Ensure methods are not annotated with `@Disabled`. |
| JUnit 5 | `methodsShouldBeAnnotatedWithDisplayName` | Ensure that test methods annotated with `@Test` or `@ParameterizedTest` are annotated with `@DisplayName`. |
| JUnit 5 | `methodsShouldMatch` | Ensure that test methods annotated with `@Test` or `@ParameterizedTest` have names matching a specific regex pattern. |
| JUnit 5 | `methodsShouldNotDeclareExceptions` | Ensure that test methods annotated with `@Test` or `@ParameterizedTest` do not declare any thrown exceptions. |
| JUnit 5 | `methodsShouldContainAssertionsOrVerifications` | Ensure that test methods annotated with `@Test` or `@ParameterizedTest` contain at least one assertion or verification. |

### Spring Rules

Expand Down Expand Up @@ -534,6 +535,28 @@ Taikai.builder()
.check();
```

- **Ensure Test Methods Contain Assertions or Verifications**: : Ensure that test methods annotated with `@Test` or `@ParameterizedTest` contain at least one assertion or verification.

- **JUnit 5**: Ensure the use of assertions from `org.junit.jupiter.api.Assertions`.
- **Mockito**: Ensure the use of verification methods from `org.mockito.Mockito` like `verify`, `inOrder`, or `capture`.
- **Hamcrest**: Ensure the use of assertions from `org.hamcrest.MatcherAssert`.
- **AssertJ**: Ensure the use of assertions from `org.assertj.core.api.Assertions`.
- **Truth**: Ensure the use of assertions from `com.google.common.truth.Truth`.
- **Cucumber**: Ensure the use of assertions from `io.cucumber.java.en.Then` or `io.cucumber.java.en.Given`.
- **Spring MockMvc**: Ensure the use of assertions from `org.springframework.test.web.servlet.MockMvc` like `andExpect` or `andDo`.
- **ArchUnit**: Ensure the use of the `check` method from `com.tngtech.archunit.lang.ArchRule`.
- **Taikai**: Ensure the use of the `check` method from `com.enofex.taikai.Taikai`.

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

## 8. Spring Rules

Spring configuration involves defining constraints specific to Spring Framework usage.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.enofex.taikai.test;

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

final class ContainAssertionsOrVerifications {

private ContainAssertionsOrVerifications() {
}

static ArchCondition<JavaMethod> containAssertionsOrVerifications() {
return new ArchCondition<>("a unit test should assert or verify something") {
@Override
public void check(JavaMethod item, ConditionEvents events) {
for (JavaMethodCall call : item.getMethodCallsFromSelf()) {
if (jUnit5(call) ||
mockito(call) ||
hamcrest(call) ||
assertJ(call) ||
truth(call) ||
cucumber(call) ||
springMockMvc(call) ||
archRule(call) ||
taikai(call)
) {
return;
}
}
events.add(SimpleConditionEvent.violated(
item,
"%s does not assert or verify anything".formatted(item.getDescription()))
);
}

private boolean jUnit5(JavaMethodCall call) {
return "org.junit.jupiter.api.Assertions".equals(call.getTargetOwner().getName());
}

private boolean mockito(JavaMethodCall call) {
return "org.mockito.Mockito".equals(call.getTargetOwner().getName())
&& (call.getName().startsWith("verify")
|| "inOrder".equals(call.getName())
|| "capture".equals(call.getName()));
}

private boolean hamcrest(JavaMethodCall call) {
return "org.hamcrest.MatcherAssert".equals(call.getTargetOwner().getName());
}

private boolean assertJ(JavaMethodCall call) {
return "org.assertj.core.api.Assertions".equals(call.getTargetOwner().getName());
}

private boolean truth(JavaMethodCall call) {
return "com.google.common.truth.Truth".equals(call.getTargetOwner().getName());
}

private boolean cucumber(JavaMethodCall call) {
return "io.cucumber.java.en.Then".equals(call.getTargetOwner().getName()) ||
"io.cucumber.java.en.Given".equals(call.getTargetOwner().getName());
}

private boolean springMockMvc(JavaMethodCall call) {
return
"org.springframework.test.web.servlet.MockMvc".equals(call.getTargetOwner().getName())
&& ("andExpect".equals(call.getName()) || "andDo".equals(call.getName()));
}

private boolean archRule(JavaMethodCall call) {
return "com.tngtech.archunit.lang.ArchRule".equals(call.getTargetOwner().getName())
&& "check".equals(call.getName());
}

private boolean taikai(JavaMethodCall call) {
return "com.enofex.taikai.Taikai".equals(call.getTargetOwner().getName())
&& "check".equals(call.getName());
}
};
}
}
37 changes: 24 additions & 13 deletions src/main/java/com/enofex/taikai/test/JUnit5Configurer.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package com.enofex.taikai.test;

import static com.enofex.taikai.internal.ArchConditions.notDeclareThrownExceptions;
import static com.enofex.taikai.test.ContainAssertionsOrVerifications.containAssertionsOrVerifications;
import static com.enofex.taikai.test.JUnit5DescribedPredicates.ANNOTATION_DISABLED;
import static com.enofex.taikai.test.JUnit5DescribedPredicates.ANNOTATION_DISPLAY_NAME;
import static com.enofex.taikai.test.JUnit5DescribedPredicates.ANNOTATION_PARAMETRIZED_TEST;
import static com.enofex.taikai.test.JUnit5DescribedPredicates.ANNOTATION_TEST;
import static com.enofex.taikai.test.JUnit5DescribedPredicates.annotatedWithTestOrParameterizedTest;

import static com.tngtech.archunit.lang.conditions.ArchPredicates.are;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
Expand Down Expand Up @@ -40,18 +40,6 @@ public JUnit5Configurer methodsShouldMatch(String regex, Configuration configura
configuration));
}

public JUnit5Configurer classesShouldBePackagePrivate(String regex) {
return classesShouldBePackagePrivate(regex, CONFIGURATION);
}

public JUnit5Configurer classesShouldBePackagePrivate(String regex, Configuration configuration) {
return addRule(TaikaiRule.of(classes()
.that().areNotInterfaces().and().haveNameMatching(regex)
.should().bePackagePrivate()
.as("Classes with names matching %s should be package-private".formatted(regex)),
configuration));
}

public JUnit5Configurer methodsShouldNotDeclareExceptions() {
return methodsShouldNotDeclareExceptions(CONFIGURATION);
}
Expand Down Expand Up @@ -102,10 +90,33 @@ public JUnit5Configurer methodsShouldNotBeAnnotatedWithDisabled(Configuration co
configuration));
}

public JUnit5Configurer methodsShouldContainAssertionsOrVerifications() {
return methodsShouldContainAssertionsOrVerifications(CONFIGURATION);
}

public JUnit5Configurer methodsShouldContainAssertionsOrVerifications(Configuration configuration) {
return addRule(TaikaiRule.of(methods()
.that(are(annotatedWithTestOrParameterizedTest(true)))
.should(containAssertionsOrVerifications()), configuration));
}

public JUnit5Configurer classesShouldNotBeAnnotatedWithDisabled() {
return classesShouldNotBeAnnotatedWithDisabled(CONFIGURATION);
}

public JUnit5Configurer classesShouldBePackagePrivate(String regex) {
return classesShouldBePackagePrivate(regex, CONFIGURATION);
}

public JUnit5Configurer classesShouldBePackagePrivate(String regex, Configuration configuration) {
return addRule(TaikaiRule.of(classes()
.that().areNotInterfaces().and().haveNameMatching(regex)
.should().bePackagePrivate()
.as("Classes with names matching %s should be package-private".formatted(regex)),
configuration));
}


public JUnit5Configurer classesShouldNotBeAnnotatedWithDisabled(Configuration configuration) {
return addRule(TaikaiRule.of(noClasses()
.should().beMetaAnnotatedWith(ANNOTATION_DISABLED)
Expand Down
3 changes: 2 additions & 1 deletion src/test/java/com/enofex/taikai/ArchitectureTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ void shouldFulfilConstrains() {
.methodsShouldNotBeAnnotatedWithDisabled()
.methodsShouldMatch("should.*")
.methodsShouldBePackagePrivate()
.methodsShouldNotDeclareExceptions()))
.methodsShouldNotDeclareExceptions()
.methodsShouldContainAssertionsOrVerifications()))
.build()
.check();
}
Expand Down
1 change: 1 addition & 0 deletions src/test/java/com/enofex/taikai/Usage.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public static void main(String[] args) {
.methodsShouldBePackagePrivate()
.methodsShouldBeAnnotatedWithDisplayName()
.methodsShouldNotBeAnnotatedWithDisabled()
.methodsShouldContainAssertionsOrVerifications()
.classesShouldBePackagePrivate(".*Test")
.classesShouldNotBeAnnotatedWithDisabled()))
.spring(spring -> spring
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import static com.tngtech.archunit.lang.conditions.ArchConditions.beAnnotatedWith;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
Expand Down Expand Up @@ -45,6 +46,7 @@ private static final class TestExample {

@Test
void should() {
assertTrue(true);
}
}

Expand All @@ -53,13 +55,15 @@ private static final class ParameterizedTestExample {
@ParameterizedTest
@EmptySource
void should(String empty) {
assertTrue(true);
}
}

private static class MetaTestExample {

@TestAnnotation
void should() {
assertTrue(true);
}
}

Expand All @@ -68,14 +72,17 @@ private static final class MetaParameterizedTestExample {
@ParameterizedTestAnnotation
@EmptySource
void should(String empty) {
assertTrue(true);
}
}

@Test
private @interface TestAnnotation {

}

@ParameterizedTest
private @interface ParameterizedTestAnnotation {

}
}

0 comments on commit c66875e

Please sign in to comment.