Skip to content

Commit

Permalink
Add migration of JMockit NonStrictExpectations blocks as well as JMoc…
Browse files Browse the repository at this point in the history
…kit junit 4 runner (#546)

* Add migration of JMockit NonStrictExpectations blocks as well as JMockit junit 4 runner

* Add unit tests for NonStrictExpectations, all passing, refactor to reduce code duplication

* Add missing copyright clause

* Remove @nullable annotation because using javax or open rewrite internal annotation is not recommended as per code review

* Add missing nullable annotations

---------

Co-authored-by: Tim te Beek <tim@moderne.io>
  • Loading branch information
shivanisky and timtebeek authored Jul 5, 2024
1 parent 3211368 commit e2bb0ae
Show file tree
Hide file tree
Showing 11 changed files with 1,211 additions and 76 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ recipeDependencies {
parserClasspath("org.mockito:mockito-all:1.10.19")
parserClasspath("org.mockito:mockito-core:3.+")
parserClasspath("org.jmockit:jmockit:1.49")
parserClasspath("org.jmockit:jmockit:1.22") // last version with NonStrictExpectations
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
Expand Up @@ -29,11 +29,15 @@
import java.util.ArrayList;
import java.util.List;

import static org.openrewrite.java.testing.jmockit.JMockitBlockType.NonStrictExpectations;
import static org.openrewrite.java.testing.jmockit.JMockitBlockType.Verifications;

class JMockitBlockRewriter {

private static final String WHEN_TEMPLATE_PREFIX = "when(#{any()}).";
private static final String VERIFY_TEMPLATE_PREFIX = "verify(#{any()}";
private static final String LENIENT_TEMPLATE_PREFIX = "lenient().";

private static final String RETURN_TEMPLATE_PREFIX = "thenReturn(";
private static final String THROW_TEMPLATE_PREFIX = "thenThrow(";
private static final String LITERAL_TEMPLATE_FIELD = "#{}";
Expand Down Expand Up @@ -131,6 +135,11 @@ private void rewriteMethodInvocation(List<Statement> statementsToRewrite) {
rewriteResult(invocation, mockInvocationResults.getResults());
}

if (blockType == NonStrictExpectations) {
// no verify for NonStrictExpectations
return;
}

boolean hasTimes = false;
if (mockInvocationResults.getTimes() != null) {
hasTimes = true;
Expand All @@ -145,22 +154,19 @@ private void rewriteMethodInvocation(List<Statement> statementsToRewrite) {
rewriteVerify(invocation, mockInvocationResults.getMaxTimes(), "atMost");
}
if (!hasResults && !hasTimes) {
rewriteVerify(invocation, null, null);
rewriteVerify(invocation, null, "");
}
}

private void removeBlock() {
methodBody = JavaTemplate.builder("")
.javaParser(JavaParser.fromJavaVersion())
.build()
.apply(
new Cursor(visitor.getCursor(), methodBody),
nextStatementCoordinates
);
.apply(new Cursor(visitor.getCursor(), methodBody), nextStatementCoordinates);
if (bodyStatementIndex == 0) {
nextStatementCoordinates = methodBody.getCoordinates().firstStatement();
} else {
setNextCoordinatesAfterLastStatementAdded(0);
setNextStatementCoordinates(0);
}
}

Expand All @@ -171,17 +177,26 @@ private void rewriteResult(J.MethodInvocation invocation, List<Expression> resul
rewriteFailed = true;
return;
}
visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, "when");

List<Object> templateParams = new ArrayList<>();
templateParams.add(invocation);
templateParams.addAll(results);

methodBody = rewriteTemplate(template, templateParams, nextStatementCoordinates);
setNextCoordinatesAfterLastStatementAdded(++numStatementsAdded);
this.rewriteFailed = !rewriteTemplate(template, templateParams, nextStatementCoordinates);
if (!this.rewriteFailed) {
this.rewriteFailed = true;
setNextStatementCoordinates(++numStatementsAdded);
// do this last making sure rewrite worked and specify hasReference=false because framework cannot find static
// reference for when method invocation when lenient is added.
boolean hasReferencesForWhen = true;
if (this.blockType == NonStrictExpectations) {
visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, "lenient");
hasReferencesForWhen = false;
}
visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, "when", hasReferencesForWhen);
}
}

private void rewriteVerify(J.MethodInvocation invocation, @Nullable Expression times, @Nullable String verificationMode) {
private void rewriteVerify(J.MethodInvocation invocation, @Nullable Expression times, String verificationMode) {
if (invocation.getSelect() == null) {
// cannot write a verification statement for an invocation without a select field
return;
Expand All @@ -194,7 +209,6 @@ private void rewriteVerify(J.MethodInvocation invocation, @Nullable Expression t
}
templateParams.add(invocation.getName().getSimpleName());
String verifyTemplate = getVerifyTemplate(invocation.getArguments(), verificationMode, templateParams);

JavaCoordinates verifyCoordinates;
if (this.blockType == Verifications) {
// for Verifications, replace the Verifications block
Expand All @@ -203,21 +217,22 @@ private void rewriteVerify(J.MethodInvocation invocation, @Nullable Expression t
// for Expectations put the verify at the end of the method
verifyCoordinates = methodBody.getCoordinates().lastStatement();
}
this.rewriteFailed = !rewriteTemplate(verifyTemplate, templateParams, verifyCoordinates);
if (!this.rewriteFailed) {
if (this.blockType == Verifications) {
setNextStatementCoordinates(++numStatementsAdded); // for Expectations, verify statements added to end of method
}

methodBody = rewriteTemplate(verifyTemplate, templateParams, verifyCoordinates);
if (this.blockType == Verifications) {
setNextCoordinatesAfterLastStatementAdded(++numStatementsAdded);
}

// do this last making sure rewrite worked and specify hasReference=false because in verify case it cannot find
// the static reference in AddImport class, and getSelect() returns not null
visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, "verify", false);
if (verificationMode != null) {
visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, verificationMode);
// do this last making sure rewrite worked and specify hasReference=false because in verify case framework
// cannot find the static reference
visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, "verify", false);
if (!verificationMode.isEmpty()) {
visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, verificationMode);
}
}
}

private void setNextCoordinatesAfterLastStatementAdded(int numStatementsAdded) {
private void setNextStatementCoordinates(int numStatementsAdded) {
// the next statement coordinates are directly after the most recently written statement, calculated by
// subtracting the removed jmockit block
int nextStatementIdx = bodyStatementIndex + numStatementsAdded - 1;
Expand All @@ -228,23 +243,28 @@ private void setNextCoordinatesAfterLastStatementAdded(int numStatementsAdded) {
}
}

private J.Block rewriteTemplate(String verifyTemplate, List<Object> templateParams, JavaCoordinates
private boolean rewriteTemplate(String template, List<Object> templateParams, JavaCoordinates
rewriteCoords) {
JavaTemplate.Builder builder = JavaTemplate.builder(verifyTemplate)
int numStatementsBefore = methodBody.getStatements().size();
methodBody = JavaTemplate.builder(template)
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "mockito-core-3.12"))
.staticImports("org.mockito.Mockito.*");
return builder
.staticImports("org.mockito.Mockito.*")
.build()
.apply(
new Cursor(visitor.getCursor(), methodBody),
rewriteCoords,
templateParams.toArray()
);
return methodBody.getStatements().size() > numStatementsBefore;
}

private static @Nullable String getWhenTemplate(List<Expression> results) {
private @Nullable String getWhenTemplate(List<Expression> results) {
boolean buildingResults = false;
final StringBuilder templateBuilder = new StringBuilder(WHEN_TEMPLATE_PREFIX);
StringBuilder templateBuilder = new StringBuilder();
if (this.blockType == NonStrictExpectations) {
templateBuilder.append(LENIENT_TEMPLATE_PREFIX);
}
templateBuilder.append(WHEN_TEMPLATE_PREFIX);
for (Expression result : results) {
JavaType resultType = result.getType();
if (result instanceof J.Literal) {
Expand Down Expand Up @@ -284,10 +304,9 @@ private static void appendToTemplate(StringBuilder templateBuilder, boolean buil
templateBuilder.append(templateField);
}

private static String getVerifyTemplate(List<Expression> arguments, @Nullable String
verificationMode, List<Object> templateParams) {
StringBuilder templateBuilder = new StringBuilder("verify(#{any()}"); // eg verify(object
if (verificationMode != null) {
private static String getVerifyTemplate(List<Expression> arguments, String verificationMode, List<Object> templateParams) {
StringBuilder templateBuilder = new StringBuilder(VERIFY_TEMPLATE_PREFIX); // eg verify(object
if (!verificationMode.isEmpty()) {
templateBuilder.append(", ").append(verificationMode).append("(#{any(int)})"); // eg verify(object, times(2)
}
templateBuilder.append(").#{}("); // eg verify(object, times(2)).method(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,28 @@
import java.util.List;
import java.util.Optional;

import static org.openrewrite.java.testing.jmockit.JMockitBlockType.*;

@Value
@EqualsAndHashCode(callSuper = false)
public class JMockitBlockToMockito extends Recipe {

@Override
public String getDisplayName() {
return "Rewrite JMockit Expectations and Verifications";
return "Rewrite JMockit Expectations, Verifications and NonStrictExpectations";
}

@Override
public String getDescription() {
return "Rewrites JMockit `Expectations and Verifications` blocks to Mockito statements.";
return "Rewrites JMockit `Expectations, Verifications and NonStrictExpectations` blocks to Mockito statements.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(Preconditions.or(
new UsesType<>(JMockitBlockType.Expectations.getFqn(), false),
new UsesType<>(JMockitBlockType.Verifications.getFqn(), false)
), new RewriteJMockitBlockVisitor());
new UsesType<>(Expectations.getFqn(), false),
new UsesType<>(Verifications.getFqn(), false),
new UsesType<>(NonStrictExpectations.getFqn(), false)), new RewriteJMockitBlockVisitor());
}

private static class RewriteJMockitBlockVisitor extends JavaIsoVisitor<ExecutionContext> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@
package org.openrewrite.java.testing.jmockit;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Getter
enum JMockitBlockType {

Expectations("mockit.Expectations"),
Verifications("mockit.Verifications"); // Add NonStrictExpectations later
Expectations,
Verifications,
NonStrictExpectations;

private final String fqn;

JMockitBlockType() {
this.fqn = "mockit." + this.name();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,9 @@ static Optional<JMockitBlockType> getJMockitBlock(Statement s) {
return empty();
}

for (JMockitBlockType blockType : JMockitBlockType.values()) {
if (TypeUtils.isAssignableTo(blockType.getFqn(), clazz.getType())) {
return Optional.of(blockType);
}
JMockitBlockType blockType = JMockitBlockType.valueOf(clazz.getSimpleName());
if (blockType != null && TypeUtils.isOfClassType(clazz.getType(), blockType.getFqn())) {
return Optional.of(blockType);
}
return empty();
}
Expand Down
Binary file not shown.
3 changes: 3 additions & 0 deletions src/main/resources/META-INF/rewrite/jmockit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ recipeList:
- org.openrewrite.java.ChangeType:
oldFullyQualifiedTypeName: mockit.integration.junit5.JMockitExtension
newFullyQualifiedTypeName: org.mockito.junit.jupiter.MockitoExtension
- org.openrewrite.java.ChangeType:
oldFullyQualifiedTypeName: mockit.integration.junit4.JMockit
newFullyQualifiedTypeName: org.mockito.junit.MockitoJUnitRunner
- org.openrewrite.java.dependencies.AddDependency:
groupId: org.mockito
artifactId: mockito-core
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,17 @@

import org.junit.jupiter.api.Test;
import org.openrewrite.DocumentExample;
import org.openrewrite.InMemoryExecutionContext;
import org.openrewrite.java.JavaParser;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;

import static org.openrewrite.java.Assertions.java;
import static org.openrewrite.java.testing.jmockit.JMockitTestUtils.setDefaultParserSettings;

class JMockitExpectationsToMockitoTest 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"
);
setDefaultParserSettings(spec);
}

@DocumentExample
Expand Down
Loading

0 comments on commit e2bb0ae

Please sign in to comment.