Skip to content

Commit

Permalink
JMockit to Mockito Migration Recipe - Rewrite Expectations Block (#415)
Browse files Browse the repository at this point in the history
* Recipe to convert JMockit Expectations block to Mockito.when statement

---------

Co-authored-by: Kun Li <kun@moderne.io>
  • Loading branch information
tinder-dthomson and kunli2 authored Oct 19, 2023
1 parent 6e58483 commit 8e1dffb
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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.+")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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<?, ExecutionContext> getVisitor() {
return Preconditions.check(new UsesType<>("mockit.*", false),
new RewriteExpectationsVisitor());
}

private static class RewriteExpectationsVisitor extends JavaVisitor<ExecutionContext> {
@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());
}
}
}
Binary file not shown.
31 changes: 31 additions & 0 deletions src/main/resources/META-INF/rewrite/jmockit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#
# Copyright 2023 the original author or authors.
# <p>
# 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
# <p>
# https://www.apache.org/licenses/LICENSE-2.0
# <p>
# 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright 2023 the original author or authors.
* <p>
* 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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());
}
}
"""
)
);
}
}

0 comments on commit 8e1dffb

Please sign in to comment.