Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JMockit's MockUp to Mockito migration #599

Open
wants to merge 49 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
b31f293
Add JMockit's MockUp to Mockito migration
SiBorea Sep 4, 2024
1da4931
Adopt mockito-core:5.+ through parserClasspath
timtebeek Sep 11, 2024
4e42b59
Apply bot code suggestions
timtebeek Sep 11, 2024
faa1bb1
Add type check for new class statement
SiBorea Sep 11, 2024
6d92ade
Merge branch 'mockup' of https://github.com/SiBorea/rewrite-testing-f…
SiBorea Sep 11, 2024
96688de
Fix statement construction
SiBorea Sep 11, 2024
f9d9fa1
Remove debug statement
SiBorea Sep 11, 2024
694f1f0
Fix code format
SiBorea Sep 12, 2024
78a4aae
Use CallRealMethod for non mock methods
SiBorea Sep 13, 2024
cda1e64
Update src/test/java/org/openrewrite/java/testing/jmockit/JMockitMock…
SiBorea Sep 13, 2024
4589bb3
Update src/test/java/org/openrewrite/java/testing/jmockit/JMockitMock…
SiBorea Sep 13, 2024
c90fb67
Update src/test/java/org/openrewrite/java/testing/jmockit/JMockitMock…
SiBorea Sep 13, 2024
7c7fecc
Update src/test/java/org/openrewrite/java/testing/jmockit/JMockitMock…
SiBorea Sep 13, 2024
8a71eb8
Update src/test/java/org/openrewrite/java/testing/jmockit/JMockitMock…
SiBorea Sep 13, 2024
f9827b0
Update src/main/java/org/openrewrite/java/testing/jmockit/JMockitMock…
SiBorea Sep 13, 2024
34589a0
Update src/main/java/org/openrewrite/java/testing/jmockit/JMockitMock…
SiBorea Sep 13, 2024
e03719a
Update src/main/java/org/openrewrite/java/testing/jmockit/JMockitMock…
SiBorea Sep 13, 2024
6c9d156
Add description about private methods
SiBorea Sep 18, 2024
3fd7446
Merge branch 'openrewrite:main' into mockup
SiBorea Sep 18, 2024
162352f
Update JMockitMockUpToMockitoTest.java
timtebeek Sep 18, 2024
a852a60
Fix UT to match gralde build order
SiBorea Sep 19, 2024
c9f8280
Merge branch 'openrewrite:main' into mockup
SiBorea Sep 21, 2024
4ef5811
Add setUp/tearDown support
SiBorea Sep 22, 2024
ac83863
Add MockUp params support
SiBorea Sep 22, 2024
e41c20d
Remove useless
SiBorea Sep 22, 2024
08f3f60
Fix JavaTemplate type inference
SiBorea Sep 23, 2024
8992a87
Remove unused local variables
SiBorea Sep 24, 2024
dce23c8
MockUp to try-with-resource
SiBorea Sep 24, 2024
e691a7b
Merge branch 'main' into mockup
SiBorea Sep 24, 2024
4eeae91
Add import class test
SiBorea Sep 26, 2024
ee06b6e
Add multiple mockUp test
SiBorea Sep 26, 2024
f06d8b0
Merge branch 'mockup' of github.com:SiBorea/rewrite-testing-framework…
SiBorea Sep 26, 2024
a9fc1bc
Apply bot suggestions
SiBorea Sep 27, 2024
9f814cb
Merge branch 'openrewrite:main' into mockup
SiBorea Oct 6, 2024
f1de48c
Remove useless type cast
SiBorea Oct 6, 2024
f987e59
Fix variable naming
SiBorea Oct 6, 2024
28485bb
Remove fqn from naming
SiBorea Oct 6, 2024
fc25bbd
Remove unneeded method call
SiBorea Oct 6, 2024
199f8f7
Shorter var naming
SiBorea Oct 6, 2024
fa85adb
Update JMockitMockUpToMockitoTest.java
timtebeek Oct 6, 2024
8391a98
Merge branch 'main' into mockup
timtebeek Oct 11, 2024
b61496d
Refactor mockup migration to resuse existing code and cleanup
shivanisky Oct 13, 2024
d9eb4a8
Merge branch 'openrewrite:main' into mockup
SiBorea Oct 15, 2024
0dc4d10
Resolve deprecated print()
SiBorea Oct 15, 2024
1e5b465
Utilize LambdaBlockToExpression
SiBorea Oct 15, 2024
b4f1e0a
Make constants
SiBorea Oct 15, 2024
ad0c387
Merge branch 'main' into mockup
shivanisky Oct 22, 2024
750a45b
Apply bot suggestions
SiBorea Oct 24, 2024
b5b1c08
Merge branch 'openrewrite:main' into mockup
SiBorea Oct 24, 2024
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
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.mockito:mockito-core:5.+")
parserClasspath("org.jmockit:jmockit:1.49")
parserClasspath("org.jmockit:jmockit:1.22") // last version with NonStrictExpectations
parserClasspath("org.mockito:mockito-junit-jupiter:3.+")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
@Getter
enum JMockitBlockType {

MockUp,
Expectations,
Verifications,
NonStrictExpectations;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
/*
* Copyright 2024 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 java.util.stream.Collectors.toList;
import org.openrewrite.ExecutionContext;
timtebeek marked this conversation as resolved.
Show resolved Hide resolved
SiBorea marked this conversation as resolved.
Show resolved Hide resolved
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.search.UsesType;
import static org.openrewrite.java.testing.jmockit.JMockitBlockType.MockUp;
import static org.openrewrite.java.tree.Flag.Static;
timtebeek marked this conversation as resolved.
Show resolved Hide resolved
SiBorea marked this conversation as resolved.
Show resolved Hide resolved
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.Statement;
import org.openrewrite.java.tree.TypeUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class JMockitMockUpToMockito extends Recipe {
timtebeek marked this conversation as resolved.
Show resolved Hide resolved
SiBorea marked this conversation as resolved.
Show resolved Hide resolved
SiBorea marked this conversation as resolved.
Show resolved Hide resolved
SiBorea marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

@shivanisky shivanisky Oct 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a look at the overall design, I'm wondering if it would be much better to reuse the existing code in JMockitBlockToMockito, and add MockUp in the Preconditions, and then in JMockitBlockRewriter if the block type is a mockup, call much of the code of this class to rewrite the method by passing the the block, visitor and context. This however, would make it difficult to handle tearing down of any mock constructors if the teardown method is before the setup method so possibly a two cycle recipe would be needed, however it would probably reduce the number of lines of code due to the reuse.

class Bla {  
   @Before
   void setup() {
       // do some migration
   }

   @After
   void teardown() {
       // do some migration based on migration of setup method above - what if this teardown method is before the setup above
   }
}

Also build needs to be green before submitting for review as the main build is green.

Copy link
Author

@SiBorea SiBorea Oct 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will it be too complicated integrating mockUp into JMockitBlockToMockito?

I can see the tests of PowerMockitoMockStaticToMockitoTest and other recipes failed and cause the build red.

image

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cloned the latest code in main and built. Those cases are failed too. @timtebeek are they expected?

SiBorea marked this conversation as resolved.
Show resolved Hide resolved
@Override
public String getDisplayName() {
return "Rewrite JMockit MockUp to Mockito statements";
}

@Override
public String getDescription() {
return "Rewrites JMockit `MockUp` blocks to Mockito statements.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new UsesType<>(MockUp.getFqn(), false), new JMockitMockUpToMockitoVisitor());
}

private static class JMockitMockUpToMockitoVisitor extends JavaIsoVisitor<ExecutionContext> {
private void appendMethod(StringBuilder sb, J.MethodDeclaration m) {
sb.append("(");
if (!m.getParameters().isEmpty()) {
for (int i = 0; i < m.getParameters().size(); i++) {
Statement s = m.getParameters().get(i);
if (s instanceof J.VariableDeclarations) {
J.VariableDeclarations param = (J.VariableDeclarations) s;
if (param.getType() instanceof JavaType.Primitive) {
switch (param.getType().toString()) {
case "int":
sb.append("anyInt()");
break;
case "long":
sb.append("anyLong()");
break;
case "double":
sb.append("anyDouble()");
break;
case "float":
sb.append("anyFloat()");
break;
case "short":
sb.append("anyShort()");
break;
case "byte":
sb.append("anyByte()");
break;
case "char":
sb.append("anyChar()");
break;
case "boolean":
sb.append("anyBoolean()");
break;
}
} else {
String paramType = param.getTypeAsFullyQualified().getClassName();
sb.append("nullable(").append(paramType).append(".class").append(")");
}
}
if (i < m.getParameters().size() - 1) {
sb.append(", ");
}
}
}
sb.append(")").append(")");
sb.append(".thenAnswer(invocation -> {");
for (Statement s : m.getBody().getStatements()) {
sb.append(s.printTrimmed());
sb.append(";");
}
sb.append("});");
}

@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDeclaration, ExecutionContext ctx) {
J.MethodDeclaration md = super.visitMethodDeclaration(methodDeclaration, ctx);
if (md.getBody() == null) {
return md;
}

// Create the new statement
J.Block body = md.getBody();
List<String> shouldClose = new ArrayList<>();
for (Statement statement : body.getStatements()) {
if (!(statement instanceof J.NewClass)) {
continue;
}
SiBorea marked this conversation as resolved.
Show resolved Hide resolved

J.NewClass newClass = (J.NewClass) statement;
if (newClass.getClazz() == null || newClass.getBody() == null) {
continue;
}

JavaType mockType = ((J.ParameterizedType) newClass.getClazz()).getTypeParameters().get(0).getType();
String className = TypeUtils.asFullyQualified(mockType).getClassName();
String mockName = className.replace(".", "_");
StringBuilder mockStatements = new StringBuilder();

List<Statement> mockedMethods = newClass.getBody()
.getStatements()
.stream()
.filter(s -> s instanceof J.MethodDeclaration)
.filter(s -> ((J.MethodDeclaration) s).getLeadingAnnotations().stream()
.anyMatch(o -> TypeUtils.isOfClassType(o.getType(), "mockit.Mock")))
// Ignore void methods
.filter(s -> !"void".equals(((J.MethodDeclaration) s).getReturnTypeExpression().toString()))
.collect(toList());

Set<String> staticMethods = ((JavaType.Class) mockType)
.getMethods()
.stream()
.filter(m -> m.getFlags().contains(Static))
.map(JavaType.Method::getName)
.collect(Collectors.toSet());

// Static
List<Statement> methods = mockedMethods
.stream()
.filter(s -> staticMethods.contains(((J.MethodDeclaration) s).getSimpleName()))
.collect(toList());
if (!methods.isEmpty()) {
mockStatements.append("MockedStatic<").append(className).append("> mockStatic").append(mockName).append(" = mockStatic(").append(className).append(".class);");
for (Statement method : methods) {
J.MethodDeclaration m = (J.MethodDeclaration) method;
mockStatements.append("mockStatic").append(mockName).append(".when(() -> ").append(className).append(".").append(m.getSimpleName());
appendMethod(mockStatements, m);
}
maybeAddImport("org.mockito.MockedStatic");
shouldClose.add("mockStatic" + mockName);
}

// Instance
methods = mockedMethods
.stream()
.filter(s -> !staticMethods.contains(((J.MethodDeclaration) s).getSimpleName()))
.collect(toList());
if (!methods.isEmpty()) {
mockStatements.append("MockedConstruction<").append(className).append("> mockObj").append(mockName).append(" = mockConstruction(").append(className).append(".class, (mock, context) -> {");
for (Statement method : methods) {
J.MethodDeclaration m = (J.MethodDeclaration) method;
mockStatements.append("when(mock.").append(m.getSimpleName());
appendMethod(mockStatements, m);
}
mockStatements.append("});");

maybeAddImport("org.mockito.MockedConstruction", null, false);
shouldClose.add("mockObj" + mockName);
}

StringBuilder otherStatements = new StringBuilder();
newClass.getBody()
.getStatements()
.stream()
.filter(s -> {
if (s instanceof J.MethodDeclaration) {
return ((J.MethodDeclaration) s).getLeadingAnnotations().stream()
.noneMatch(o -> TypeUtils.isOfClassType(o.getType(), "mockit.Mock"));
}
return true;
})
.forEach(s -> {
otherStatements.append(s.printTrimmed());
otherStatements.append(";");
});
SiBorea marked this conversation as resolved.
Show resolved Hide resolved

JavaTemplate tpl = JavaTemplate
.builder(otherStatements.toString() + mockStatements)
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "mockito-core-5"))
.imports("org.mockito.MockedStatic", "org.mockito.MockedConstruction")
.staticImports("org.mockito.Mockito.*")
.contextSensitive()
.build();
md = maybeAutoFormat(md, tpl.apply(updateCursor(md), statement.getCoordinates().replace()), ctx);
}

for (String o : shouldClose) {
JavaTemplate tpl = JavaTemplate
.builder(o + ".close();")
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "mockito-core-5"))
.imports("org.mockito.MockedStatic", "org.mockito.MockedConstruction")
.staticImports("org.mockito.Mockito.*")
.contextSensitive()
.build();
md = maybeAutoFormat(md, tpl.apply(updateCursor(md), md.getBody().getCoordinates().lastStatement()), ctx);
}

maybeAddImport("org.mockito.Mockito", "*", false);
maybeRemoveImport("mockit.Mock");
maybeRemoveImport("mockit.MockUp");

return md;
}
}
}
Binary file not shown.
1 change: 1 addition & 0 deletions src/main/resources/META-INF/rewrite/jmockit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ tags:
- jmockit
recipeList:
- org.openrewrite.java.testing.jmockit.JMockitBlockToMockito
- org.openrewrite.java.testing.jmockit.JMockitMockUpToMockito
- org.openrewrite.java.testing.jmockit.JMockitAnnotatedArgumentToMockito
- org.openrewrite.java.ChangeType:
oldFullyQualifiedTypeName: mockit.Mocked
Expand Down
Loading
Loading