Skip to content

Commit 63820f4

Browse files
authored
Add JUnit 4 support to AddMockitoExtensionIfAnnotationsUsed (#1932) (#922)
When PowerMock's @RunWith(PowerMockRunner.class) is removed from JUnit 4 tests, mock fields annotated with @mock are never initialized. This adds @RunWith(MockitoJUnitRunner.class) for JUnit 4 tests using Mockito annotations, complementing the existing JUnit 5 @ExtendWith support.
1 parent e8bbdf0 commit 63820f4

2 files changed

Lines changed: 103 additions & 19 deletions

File tree

src/main/java/org/openrewrite/java/testing/mockito/AddMockitoExtensionIfAnnotationsUsed.java

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,14 @@ public class AddMockitoExtensionIfAnnotationsUsed extends Recipe {
4141
final String displayName = "Adds Mockito extensions to Mockito tests";
4242

4343
@Getter
44-
final String description = "Adds `@ExtendWith(MockitoExtension.class)` to tests using `@Mock` or `@Captor`.";
44+
final String description = "Adds `@ExtendWith(MockitoExtension.class)` to JUnit 5 tests or " +
45+
"`@RunWith(MockitoJUnitRunner.class)` to JUnit 4 tests using Mockito annotations like `@Mock` or `@Captor`.";
4546

4647
@Override
4748
public TreeVisitor<?, ExecutionContext> getVisitor() {
4849

4950
TreeVisitor<?, ExecutionContext> hasExtendedWithAnnotation = new FindAnnotations("org.junit.jupiter.api.extension.ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class)", false).getVisitor();
51+
TreeVisitor<?, ExecutionContext> hasRunWithAnnotation = new FindAnnotations("org.junit.runner.RunWith", false).getVisitor();
5052
@SuppressWarnings("unchecked")
5153
TreeVisitor<?, ExecutionContext>[] hasAnyMockitoAnnotation = new TreeVisitor[]{
5254
// see https://www.baeldung.com/mockito-annotations for examples
@@ -57,16 +59,23 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
5759
};
5860

5961
return check(and(new IsLikelyTest().getVisitor(),
60-
// check to only migrate JUnit 5 tests
61-
new FindTypes("org.junit.jupiter..*", false).getVisitor(),
62-
// prevent addition if present
63-
not(hasExtendedWithAnnotation),
64-
or(hasAnyMockitoAnnotation)),
62+
or(hasAnyMockitoAnnotation),
63+
or(
64+
// JUnit 5: has JUnit 5 types and no @ExtendWith(MockitoExtension.class)
65+
and(new FindTypes("org.junit.jupiter..*", false).getVisitor(),
66+
not(hasExtendedWithAnnotation)),
67+
// JUnit 4: has @org.junit.Test, no @RunWith
68+
and(new FindAnnotations("org.junit.Test", false).getVisitor(),
69+
not(hasRunWithAnnotation))
70+
)),
6571
new TreeVisitor<Tree, ExecutionContext>() {
6672
@Override
6773
public @Nullable Tree preVisit(Tree tree, ExecutionContext ctx) {
6874
stopAfterPreVisit();
6975
if (tree instanceof J.CompilationUnit) {
76+
if (!FindAnnotations.find((J) tree, "@org.junit.Test").isEmpty()) {
77+
return getJunit4JavaVisitor().visit(tree, ctx);
78+
}
7079
return getJavaVisitor().visit(tree, ctx);
7180
}
7281
if (tree instanceof K.CompilationUnit) {
@@ -94,6 +103,23 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex
94103
};
95104
}
96105

106+
private JavaIsoVisitor<ExecutionContext> getJunit4JavaVisitor() {
107+
return new JavaIsoVisitor<ExecutionContext>() {
108+
@Override
109+
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
110+
maybeAddImport("org.mockito.junit.MockitoJUnitRunner");
111+
maybeAddImport("org.junit.runner.RunWith");
112+
113+
return JavaTemplate.builder("@RunWith(MockitoJUnitRunner.class)")
114+
.imports("org.mockito.junit.MockitoJUnitRunner")
115+
.imports("org.junit.runner.RunWith")
116+
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "junit-4", "mockito-core"))
117+
.build()
118+
.apply(getCursor(), classDecl.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName)));
119+
}
120+
};
121+
}
122+
97123
private KotlinIsoVisitor<ExecutionContext> getKotlinVisitor() {
98124
return new KotlinIsoVisitor<ExecutionContext>() {
99125
@Override

src/test/java/org/openrewrite/java/testing/mockito/AddMockitoExtensionIfAnnotationsUsedTest.java

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class AddMockitoExtensionIfAnnotationsUsedTest implements RewriteTest {
3333
public void defaults(RecipeSpec spec) {
3434
spec.recipe(new AddMockitoExtensionIfAnnotationsUsed())
3535
.parser(JavaParser.fromJavaVersion()
36-
.classpathFromResources(new InMemoryExecutionContext(), "junit-jupiter-api", "mockito-junit-jupiter", "mockito-core")
36+
.classpathFromResources(new InMemoryExecutionContext(), "junit-4", "junit-jupiter-api", "mockito-junit-jupiter", "mockito-core")
3737
.dependsOn("public class Service {}"))
3838
.parser(KotlinParser.builder()
3939
.classpathFromResources(new InMemoryExecutionContext(), "junit-jupiter-api", "mockito-junit-jupiter", "mockito-core")
@@ -42,7 +42,7 @@ public void defaults(RecipeSpec spec) {
4242

4343
@DocumentExample
4444
@Test
45-
void addForMock() {
45+
void addForMockWithJUnit5() {
4646
rewriteRun(
4747
//language=java
4848
java(
@@ -76,7 +76,7 @@ void test() {}
7676
}
7777

7878
@Test
79-
void addForCaptor() {
79+
void addForCaptorWithJUnit5() {
8080
rewriteRun(
8181
//language=java
8282
java(
@@ -110,7 +110,7 @@ void test() {}
110110
}
111111

112112
@Test
113-
void dontAddIfPresent() {
113+
void doNotAddIfPresentWithJUnit5() {
114114
rewriteRun(
115115
//language=java
116116
java(
@@ -133,29 +133,87 @@ class Test {
133133
}
134134

135135
@Test
136-
void dontAddIfJunit4() {
136+
void addForMockWithJUnit4() {
137137
rewriteRun(
138138
//language=java
139139
java(
140140
"""
141141
import org.junit.Test;
142-
import org.junit.jupiter.api.extension.ExtendWith;
143142
import org.mockito.Mock;
144-
import org.mockito.junit.jupiter.MockitoExtension;
145143
146-
class Test {
144+
public class MyTest {
147145
@Mock
148146
Service service;
149147
@Test
150-
void test() {}
148+
public void test() {}
149+
}
150+
""",
151+
"""
152+
import org.junit.Test;
153+
import org.junit.runner.RunWith;
154+
import org.mockito.Mock;
155+
import org.mockito.junit.MockitoJUnitRunner;
156+
157+
@RunWith(MockitoJUnitRunner.class)
158+
public class MyTest {
159+
@Mock
160+
Service service;
161+
@Test
162+
public void test() {}
163+
}
164+
"""
165+
)
166+
);
167+
}
168+
169+
@Test
170+
void doNotAddIfPresentWithJUnit4() {
171+
rewriteRun(
172+
//language=java
173+
java(
174+
"""
175+
import org.junit.Test;
176+
import org.junit.runner.RunWith;
177+
import org.mockito.Mock;
178+
import org.mockito.junit.MockitoJUnitRunner;
179+
180+
@RunWith(MockitoJUnitRunner.class)
181+
public class MyTest {
182+
@Mock
183+
Service service;
184+
@Test
185+
public void test() {}
151186
}
152187
"""
153188
)
154189
);
155190
}
156191

157192
@Test
158-
void notInferWithExistingAnnotations() {
193+
void doNotAddIfOtherRunnerPresentWithJUnit4() {
194+
rewriteRun(
195+
//language=java
196+
java(
197+
"""
198+
import org.junit.Test;
199+
import org.junit.runner.RunWith;
200+
import org.junit.runners.JUnit4;
201+
import org.mockito.Mock;
202+
203+
@RunWith(JUnit4.class)
204+
public class MyTest {
205+
@Mock
206+
Service service;
207+
@Test
208+
public void test() {}
209+
}
210+
"""
211+
)
212+
);
213+
}
214+
215+
@Test
216+
void addWithExistingAnnotationsWithJUnit5() {
159217
rewriteRun(
160218
//language=java
161219
java(
@@ -193,7 +251,7 @@ void test() {}
193251
}
194252

195253
@Test
196-
void dontAddIfPresentInKotlin() {
254+
void doNotAddIfPresentInKotlinWithJUnit5() {
197255
rewriteRun(
198256
spec -> spec.parser(KotlinParser.builder()
199257
.classpathFromResources(new InMemoryExecutionContext(), "junit-jupiter-api", "mockito-junit-jupiter", "mockito-core")),
@@ -219,9 +277,9 @@ fun test() {}
219277
}
220278

221279
@Test
222-
void addForMockKotlin() {
280+
void addForMockKotlinWithJUnit5() {
223281
rewriteRun(
224-
//language=java
282+
//language=kotlin
225283
kotlin(
226284
"""
227285
import org.junit.jupiter.api.Test

0 commit comments

Comments
 (0)