diff --git a/build.gradle.kts b/build.gradle.kts index 27540a31c..5c9e144b4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,6 +19,7 @@ recipeDependencies { parserClasspath("com.github.tomakehurst:wiremock-jre8:2.35.0") parserClasspath("org.mockito:mockito-all:1.10.19") parserClasspath("org.mockito:mockito-core:3.+") + parserClasspath("org.jmockit:jmockit:1.49") parserClasspath("org.mockito:mockito-junit-jupiter:3.+") parserClasspath("org.powermock:powermock-api-mockito:1.7.+") parserClasspath("org.powermock:powermock-core:1.7.+") diff --git a/src/main/java/org/openrewrite/java/testing/jmockit/JMockitExpectationsToMockitoWhen.java b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitExpectationsToMockitoWhen.java new file mode 100644 index 000000000..3ef35a195 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitExpectationsToMockitoWhen.java @@ -0,0 +1,93 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.testing.jmockit; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.Statement; + +@Value +@EqualsAndHashCode(callSuper = false) +public class JMockitExpectationsToMockitoWhen extends Recipe { + @Override + public String getDisplayName() { + return "Rewrite JMockit Expectations"; + } + + @Override + public String getDescription() { + return "Rewrites JMockit `Expectations` to `Mockito.when`."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesType<>("mockit.*", false), + new RewriteExpectationsVisitor()); + } + + private static class RewriteExpectationsVisitor extends JavaVisitor { + @Override + public J visitNewClass(J.NewClass newClass, ExecutionContext executionContext) { + J.NewClass nc = (J.NewClass) super.visitNewClass(newClass, executionContext); + if (!(nc.getClazz() instanceof J.Identifier)) { + return nc; + } + J.Identifier clazz = (J.Identifier) nc.getClazz(); + if (!clazz.getSimpleName().equals("Expectations")) { + return nc; + } + + // empty Expectations block is considered invalid + assert nc.getBody() != null : "Expectations block is empty"; + + // prepare the statements for moving + J.Block innerBlock = (J.Block) nc.getBody().getStatements().get(0); + + // TODO: handle multiple mock statements + Statement mockInvocation = innerBlock.getStatements().get(0); + Expression result = ((J.Assignment) innerBlock.getStatements().get(1)).getAssignment(); + + // apply the template and replace the `new Expectations()` statement coordinates + // TODO: handle exception results with another template + J.MethodInvocation newMethod = JavaTemplate.builder("when(#{any()}).thenReturn(#{});") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(executionContext, "mockito-core-3.12")) + .staticImports("org.mockito.Mockito.when") + .build() + .apply( + getCursor(), + nc.getCoordinates().replace(), + mockInvocation, + result + ); + + // handle import changes + maybeAddImport("org.mockito.Mockito", "when"); + maybeRemoveImport("mockit.Expectations"); + + return newMethod.withPrefix(nc.getPrefix()); + } + } +} diff --git a/src/main/resources/META-INF/rewrite/classpath/jmockit-1.49.jar b/src/main/resources/META-INF/rewrite/classpath/jmockit-1.49.jar new file mode 100644 index 000000000..3306c225f Binary files /dev/null and b/src/main/resources/META-INF/rewrite/classpath/jmockit-1.49.jar differ diff --git a/src/main/resources/META-INF/rewrite/jmockit.yml b/src/main/resources/META-INF/rewrite/jmockit.yml new file mode 100644 index 000000000..d87cecf59 --- /dev/null +++ b/src/main/resources/META-INF/rewrite/jmockit.yml @@ -0,0 +1,31 @@ +# +# Copyright 2023 the original author or authors. +#

+# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +#

+# https://www.apache.org/licenses/LICENSE-2.0 +#

+# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.testing.jmockit.JMockitToMockito +displayName: Migrate from JMockit to Mockito +description: This recipe will apply changes commonly needed when migrating from JMockit to Mockito. +tags: + - testing + - jmockit +recipeList: + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: mockit.Mocked + newFullyQualifiedTypeName: org.mockito.Mock + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: mockit.integration.junit5.JMockitExtension + newFullyQualifiedTypeName: org.mockito.junit.jupiter.MockitoExtension + - org.openrewrite.java.testing.jmockit.JMockitExpectationsToMockitoWhen diff --git a/src/test/java/org/openrewrite/java/testing/jmockit/JMockitToMockitoTest.java b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitToMockitoTest.java new file mode 100644 index 000000000..dd7ae8ea9 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitToMockitoTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2023 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.testing.jmockit; + +import static org.openrewrite.java.Assertions.java; + +import org.junit.jupiter.api.Test; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +class JMockitToMockitoTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec + .parser(JavaParser.fromJavaVersion() + .logCompilationWarningsAndErrors(true) + .classpathFromResources(new InMemoryExecutionContext(), + "junit-jupiter-api-5.9", + "jmockit-1.49", + "mockito-core-3.12", + "mockito-junit-jupiter-3.12" + )) + .recipeFromResource( + "/META-INF/rewrite/jmockit.yml", + "org.openrewrite.java.testing.jmockit.JMockitToMockito" + ); + } + + @Test + void jMockitExpectationsToMockitoWhen() { + //language=java + rewriteRun( + java( + """ + class MyObject { + public String getSomeField() { + return "X"; + } + } + """ + ), + java( + """ + import static org.junit.jupiter.api.Assertions.assertNull; + + import mockit.Expectations; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() { + new Expectations() {{ + myObject.getSomeField(); + result = null; + }}; + assertNull(myObject.getSomeField()); + } + } + """, + """ + import static org.junit.jupiter.api.Assertions.assertNull; + import static org.mockito.Mockito.when; + + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + MyObject myObject; + + void test() { + when(myObject.getSomeField()).thenReturn(null); + assertNull(myObject.getSomeField()); + } + } + """ + ) + ); + } +}