Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import lombok.Value;
import org.jspecify.annotations.Nullable;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Option;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
Expand All @@ -28,6 +29,7 @@
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.MethodMatcher;
import org.openrewrite.java.search.FindImports;
import org.openrewrite.java.search.UsesType;
import org.openrewrite.java.trait.Annotated;
import org.openrewrite.java.trait.MethodAccess;
Expand Down Expand Up @@ -57,14 +59,22 @@ public class ReplaceMockitoTestExecutionListener extends Recipe {
private static final AnnotationMatcher AFTER_METHOD_MATCHER =
new AnnotationMatcher("@org.testng.annotations.AfterMethod");

@Option(displayName = "Target framework",
description = "The test framework to use when imports alone cannot determine the framework. " +
"Typically set by wrapper recipes that check project dependencies.",
valid = {"jupiter", "junit4", "testng"},
required = false)
@Nullable
String targetFramework;

String displayName = "Replace `MockitoTestExecutionListener` with the equivalent Mockito test initialization";

String description = "Replace `@TestExecutionListeners(MockitoTestExecutionListener.class)` with the appropriate " +
"Mockito initialization for the test framework in use: `@ExtendWith(MockitoExtension.class)` for JUnit 5, " +
"`@RunWith(MockitoJUnitRunner.class)` for JUnit 4, or `MockitoAnnotations.openMocks(this)` for TestNG.";

private enum TestFramework {
JUNIT5, JUNIT4, TESTNG
JUPITER, JUNIT4, TESTNG
}

@Override
Expand All @@ -79,18 +89,18 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDeclarat
return cd;
}

TestFramework framework = detectFramework(cd);
TestFramework framework = detectFramework(cd, ctx);

// Skip if replacement already exists or can't be added
if (framework == TestFramework.JUNIT5 && context.extendWithMockitoFound) {
if (framework == TestFramework.JUPITER && context.extendWithMockitoFound) {
return cd;
}
if (framework == TestFramework.JUNIT4 && context.runWithFound) {
return cd;
}
// Add replacement based on framework
switch (framework) {
case JUNIT5:
case JUPITER:
cd = JavaTemplate.builder("@ExtendWith(MockitoExtension.class)")
.imports("org.junit.jupiter.api.extension.ExtendWith", "org.mockito.junit.jupiter.MockitoExtension")
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "junit-jupiter-api", "mockito-junit-jupiter"))
Expand Down Expand Up @@ -186,27 +196,39 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDeclarat
}));
}

private TestFramework detectFramework(J.ClassDeclaration cd) {
// Check extends for TestNG base classes
private TestFramework detectFramework(J.ClassDeclaration cd, ExecutionContext ctx) {
// Structural evidence always wins
if (cd.getExtends() != null &&
TypeUtils.isAssignableTo(ABSTRACT_TESTNG_SPRING, cd.getExtends().getType())) {
return TestFramework.TESTNG;
}

// Check compilation unit imports
// Import-based detection
J.CompilationUnit cu = getCursor().firstEnclosingOrThrow(J.CompilationUnit.class);
for (J.Import imp : cu.getImports()) {
String pkg = imp.getPackageName();
if (pkg.startsWith("org.junit") && !pkg.startsWith("org.junit.jupiter")) {
return TestFramework.JUNIT4;
}
if (pkg.startsWith("org.testng")) {
return TestFramework.TESTNG;
if (new FindImports("org.junit.jupiter..*", null).getVisitor().visit(cu, ctx) != cu) {
return TestFramework.JUPITER;
}
if (new FindImports("org.junit..*", null).getVisitor().visit(cu, ctx) != cu) {
return TestFramework.JUNIT4;
}
if (new FindImports("org.testng..*", null).getVisitor().visit(cu, ctx) != cu) {
return TestFramework.TESTNG;
}

// Dependency-based fallback from YAML wrapper recipes
if (targetFramework != null) {
switch (targetFramework) {
case "junit5":
return TestFramework.JUPITER;
case "junit4":
return TestFramework.JUNIT4;
case "testng":
return TestFramework.TESTNG;
}
}

// Default to JUnit 5
return TestFramework.JUNIT5;
return TestFramework.JUPITER;
}
});
}
Expand Down
46 changes: 45 additions & 1 deletion src/main/resources/META-INF/rewrite/mockito.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,9 @@ recipeList:
- org.openrewrite.java.testing.mockito.MockUtilsToStatic
- org.openrewrite.java.testing.junit5.MockitoJUnitToMockitoExtension
- org.openrewrite.java.testing.mockito.AddMockitoExtensionIfAnnotationsUsed
- org.openrewrite.java.testing.mockito.ReplaceMockitoTestExecutionListener
- org.openrewrite.java.testing.mockito.ReplaceMockitoTestExecutionListenerForJupiter
- org.openrewrite.java.testing.mockito.ReplaceMockitoTestExecutionListenerForJUnit4
- org.openrewrite.java.testing.mockito.ReplaceMockitoTestExecutionListenerForTestNG
- org.openrewrite.java.testing.mockito.RemoveInitMocksIfRunnersSpecified
- org.openrewrite.java.testing.mockito.ReplacePowerMockito
- org.openrewrite.java.dependencies.ChangeDependency:
Expand All @@ -199,3 +201,45 @@ recipeList:
- org.openrewrite.java.ChangeMethodName:
methodPattern: org.mockito.Mockito anyObject()
newMethodName: any
---
type: specs.openrewrite.org/v1beta/recipe
name: org.openrewrite.java.testing.mockito.ReplaceMockitoTestExecutionListenerForJupiter
displayName: Replace `MockitoTestExecutionListener` (JUnit Jupiter projects)
description: >-
Replace `MockitoTestExecutionListener` in projects that have JUnit Jupiter as a dependency.
Uses `@ExtendWith(MockitoExtension.class)` as the replacement.
preconditions:
- org.openrewrite.java.dependencies.search.ModuleHasDependency:
groupIdPattern: org.junit.jupiter
artifactIdPattern: junit-jupiter*
recipeList:
- org.openrewrite.java.testing.mockito.ReplaceMockitoTestExecutionListener:
targetFramework: jupiter
---
type: specs.openrewrite.org/v1beta/recipe
name: org.openrewrite.java.testing.mockito.ReplaceMockitoTestExecutionListenerForJUnit4
displayName: Replace `MockitoTestExecutionListener` (JUnit 4 projects)
description: >-
Replace `MockitoTestExecutionListener` in projects that have JUnit 4 as a dependency.
Uses `@RunWith(MockitoJUnitRunner.class)` as the replacement.
preconditions:
- org.openrewrite.java.dependencies.search.ModuleHasDependency:
groupIdPattern: junit
artifactIdPattern: junit
recipeList:
- org.openrewrite.java.testing.mockito.ReplaceMockitoTestExecutionListener:
targetFramework: junit4
---
type: specs.openrewrite.org/v1beta/recipe
name: org.openrewrite.java.testing.mockito.ReplaceMockitoTestExecutionListenerForTestNG
displayName: Replace `MockitoTestExecutionListener` (TestNG projects)
description: >-
Replace `MockitoTestExecutionListener` in projects that have TestNG as a dependency.
Uses `MockitoAnnotations.openMocks(this)` with `@BeforeMethod`/`@AfterMethod` as the replacement.
preconditions:
- org.openrewrite.java.dependencies.search.ModuleHasDependency:
groupIdPattern: org.testng
artifactIdPattern: testng*
recipeList:
- org.openrewrite.java.testing.mockito.ReplaceMockitoTestExecutionListener:
targetFramework: testng
Loading
Loading