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

Jmockit mockito full verifications and Refactor tests #617

Merged
merged 11 commits into from
Oct 22, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@
import java.util.ArrayList;
import java.util.List;

import static org.openrewrite.java.testing.jmockit.JMockitBlockType.FullVerifications;
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 VERIFY_NO_INTERACTIONS_TEMPLATE_PREFIX = "verifyNoMoreInteractions(";
private static final String LENIENT_TEMPLATE_PREFIX = "lenient().";

private static final String RETURN_TEMPLATE_PREFIX = "thenReturn(";
Expand Down Expand Up @@ -95,17 +96,27 @@ J.Block rewriteMethodBody() {

// iterate over the statements and build a list of grouped method invocations and related statements eg times
List<List<Statement>> methodInvocationsToRewrite = new ArrayList<>();
List<J.Identifier> uniqueMocks = new ArrayList<>();
int methodInvocationIdx = -1;
for (Statement jmockitBlockStatement : jmockitBlock.getStatements()) {
if (jmockitBlockStatement instanceof J.MethodInvocation) {
// ensure it's not a returns statement, we add that later to related statements
J.MethodInvocation invocation = (J.MethodInvocation) jmockitBlockStatement;
if (invocation.getSelect() != null && !invocation.getName().getSimpleName().equals("returns")) {
methodInvocationIdx++;
methodInvocationsToRewrite.add(new ArrayList<>());
Expression select = invocation.getSelect();
if (select instanceof J.Identifier) {
J.Identifier mockObj = (J.Identifier) select;
// ensure it's not a returns statement, we add that later to related statements
if (!invocation.getName().getSimpleName().equals("returns")) {
methodInvocationIdx++;
methodInvocationsToRewrite.add(new ArrayList<>());
}
if (isFullVerifications() && uniqueMocks.stream().noneMatch(mock -> mock.getType().equals(mockObj.getType())
&& mock.getSimpleName().equals(mockObj.getSimpleName()))) {
uniqueMocks.add(mockObj);
}
}
}

// add the statements corresponding to the method invocation
if (methodInvocationIdx != -1) {
methodInvocationsToRewrite.get(methodInvocationIdx).add(jmockitBlockStatement);
}
Expand All @@ -118,9 +129,17 @@ J.Block rewriteMethodBody() {

// now rewrite
methodInvocationsToRewrite.forEach(this::rewriteMethodInvocation);

if (isFullVerifications()) {
rewriteFullVerify(new ArrayList<>(uniqueMocks));
}
return methodBody;
}

private boolean isFullVerifications() {
return this.blockType == FullVerifications;
}

private void rewriteMethodInvocation(List<Statement> statementsToRewrite) {
final MockInvocationResults mockInvocationResults = buildMockInvocationResults(statementsToRewrite);
if (mockInvocationResults == null) {
Expand All @@ -136,7 +155,7 @@ private void rewriteMethodInvocation(List<Statement> statementsToRewrite) {
rewriteResult(invocation, mockInvocationResults.getResults(), hasTimes);
}

if (!hasResults && !hasTimes && (this.blockType == JMockitBlockType.Expectations || this.blockType == Verifications)) {
if (!hasResults && !hasTimes && (this.blockType == JMockitBlockType.Expectations || this.blockType.isVerifications())) {
rewriteVerify(invocation, null, "");
return;
}
Expand Down Expand Up @@ -171,7 +190,7 @@ private void rewriteResult(J.MethodInvocation invocation, List<Expression> resul
List<Object> templateParams = new ArrayList<>();
templateParams.add(invocation);
templateParams.addAll(results);
this.rewriteFailed = !rewriteTemplate(template, templateParams, nextStatementCoordinates);
rewriteTemplate(template, templateParams, nextStatementCoordinates);
if (this.rewriteFailed) {
return;
}
Expand Down Expand Up @@ -199,19 +218,19 @@ 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) {
if (this.blockType.isVerifications()) {
// for Verifications, replace the Verifications block
verifyCoordinates = nextStatementCoordinates;
} else {
// for Expectations put the verify at the end of the method
verifyCoordinates = methodBody.getCoordinates().lastStatement();
}
this.rewriteFailed = !rewriteTemplate(verifyTemplate, templateParams, verifyCoordinates);
rewriteTemplate(verifyTemplate, templateParams, verifyCoordinates);
if (this.rewriteFailed) {
return;
}

if (this.blockType == Verifications) {
if (this.blockType.isVerifications()) {
setNextStatementCoordinates(++numStatementsAdded); // for Expectations, verify statements added to end of method
}

Expand All @@ -223,6 +242,20 @@ private void rewriteVerify(J.MethodInvocation invocation, @Nullable Expression t
}
}

private void rewriteFullVerify(List<Object> mocks) {
if (!mocks.isEmpty()) {
StringBuilder sb = new StringBuilder(VERIFY_NO_INTERACTIONS_TEMPLATE_PREFIX);
mocks.forEach(mock -> sb.append(ANY_TEMPLATE_FIELD).append(",")); // verifyNoMoreInteractions(mock1, mock2 ...
sb.deleteCharAt(sb.length() - 1);
sb.append(")");
rewriteTemplate(sb.toString(), mocks, nextStatementCoordinates);
if (!this.rewriteFailed) {
setNextStatementCoordinates(++numStatementsAdded);
visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, "verifyNoMoreInteractions", false);
}
}
}

private void setNextStatementCoordinates(int numStatementsAdded) {
if (numStatementsAdded <= 0 && bodyStatementIndex == 0) {
nextStatementCoordinates = methodBody.getCoordinates().firstStatement();
Expand All @@ -240,7 +273,7 @@ private void setNextStatementCoordinates(int numStatementsAdded) {
this.nextStatementCoordinates = this.methodBody.getStatements().get(lastStatementIdx).getCoordinates().after();
}

private boolean rewriteTemplate(String template, List<Object> templateParams, JavaCoordinates
private void rewriteTemplate(String template, List<Object> templateParams, JavaCoordinates
rewriteCoords) {
int numStatementsBefore = methodBody.getStatements().size();
methodBody = JavaTemplate.builder(template)
Expand All @@ -252,7 +285,7 @@ private boolean rewriteTemplate(String template, List<Object> templateParams, Ja
rewriteCoords,
templateParams.toArray()
);
return methodBody.getStatements().size() > numStatementsBefore;
this.rewriteFailed = methodBody.getStatements().size() <= numStatementsBefore;
}

private @Nullable String getWhenTemplate(List<Expression> results, boolean lenient) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,31 +26,34 @@
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.Statement;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

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

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

private static final String SUPPORTED_TYPES = getSupportedTypesStr();

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

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

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(Preconditions.or(
new UsesType<>(Expectations.getFqn(), false),
new UsesType<>(Verifications.getFqn(), false),
new UsesType<>(NonStrictExpectations.getFqn(), false)), new RewriteJMockitBlockVisitor());
@SuppressWarnings("rawtypes")
UsesType[] usesTypes = Arrays.stream(values()).map(blockType -> new UsesType<>(blockType.getFqn(), false)).toArray(UsesType[]::new);
return Preconditions.check(Preconditions.or(usesTypes), new RewriteJMockitBlockVisitor());
}

private static class RewriteJMockitBlockVisitor extends JavaIsoVisitor<ExecutionContext> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,25 @@

import lombok.Getter;

import java.util.Arrays;

@Getter
enum JMockitBlockType {

Expectations,
NonStrictExpectations,
Verifications,
NonStrictExpectations;
FullVerifications;

private final String fqn = "mockit." + this.name();

private final String fqn;
boolean isVerifications() {
return this == Verifications || this == FullVerifications;
}

JMockitBlockType() {
this.fqn = "mockit." + this.name();
static String getSupportedTypesStr() {
StringBuilder sb = new StringBuilder();
Arrays.stream(values()).forEach(value -> sb.append(value).append(", "));
return sb.substring(0, sb.length() - 2);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,10 @@

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;

class JMockitAnnotatedArgumentToMockitoTest 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"
))
.recipeFromResource(
"/META-INF/rewrite/jmockit.yml",
"org.openrewrite.java.testing.jmockit.JMockitToMockito"
);
}
class JMockitAnnotatedArgumentToMockitoTest extends JMockitTestBase {

@DocumentExample
@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@
import org.junit.jupiter.api.Test;
import org.openrewrite.DocumentExample;
import org.openrewrite.Issue;
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;

/**
* At the moment, JMockit Delegates are not migrated to mockito. What I'm seeing is that they are being trashed
Expand All @@ -32,12 +29,7 @@
*/
@Disabled
@Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/522")
class JMockitDelegateToMockitoTest implements RewriteTest {

@Override
public void defaults(RecipeSpec spec) {
setDefaultParserSettings(spec);
}
class JMockitDelegateToMockitoTest extends JMockitTestBase {

@DocumentExample
@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,10 @@
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.openrewrite.DocumentExample;
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) {
setDefaultParserSettings(spec);
}
class JMockitExpectationsToMockitoTest extends JMockitTestBase {

@DocumentExample
@Test
Expand Down
Loading