Skip to content

Commit

Permalink
Migrate Jmockit VerificationsInOrder to Mockito (#632)
Browse files Browse the repository at this point in the history
* Migrate JMockit VerificationsInOrder to Mockito. Still seeing issue with Mockito InOrder statement. Also need to support multiple VerificationsInOrder blocks in one method.

* Revert uneeded changes, add correct Inorder statement

* Revert unneeded changes, add correct Inorder statement, add test case for multiple blocks

* Refactor ArrayList to List

Co-authored-by: Tim te Beek <[email protected]>

* Refactor

Co-authored-by: Tim te Beek <[email protected]>

* Refactor

Co-authored-by: Tim te Beek <[email protected]>

* Use correct template for multiple mocks. Refactor code to reduce duplicates

* Add support for multiple VerificationsInOrder blocks

* Add support for multiple VerificationsInOrder blocks, reformat

* Isolate single issue to one test

* Reduce warnings

* Reduce duplicate code by centralising java parser using mockito core resource jar, reduce warnings

* Fix failing test

* For now accept imperfect whitespace handling

---------

Co-authored-by: Tim te Beek <[email protected]>
  • Loading branch information
shivanisky and timtebeek authored Dec 11, 2024
1 parent f9005f1 commit b10165d
Show file tree
Hide file tree
Showing 9 changed files with 502 additions and 159 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ private Expression applyArgumentTemplate(Expression methodArgument, String argum
List<Object> templateParams) {
visitor.maybeAddImport("org.mockito.Mockito", argumentMatcher);
return JavaTemplate.builder(template)
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "mockito-core-3.12"))
.javaParser(JMockitUtils.getJavaParser(ctx))
.staticImports("org.mockito.Mockito." + argumentMatcher)
.build()
.apply(
Expand Down Expand Up @@ -232,7 +232,7 @@ private Expression rewriteAnyWithClassParameterToArgumentMatcher(Expression meth
}

private Expression applyArrayClassArgumentTemplate(Expression methodArgument, JavaType elementType) {
String newArrayElementClassName = "";
String newArrayElementClassName;
if (elementType instanceof JavaType.FullyQualified) {
newArrayElementClassName = ((JavaType.FullyQualified) elementType).getClassName();
} else if (elementType instanceof JavaType.Primitive) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import org.openrewrite.TreeVisitor;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.search.FindAnnotations;
import org.openrewrite.java.search.UsesType;
Expand All @@ -32,6 +31,8 @@
import java.util.ArrayList;
import java.util.List;

import static org.openrewrite.java.testing.jmockit.JMockitUtils.getJavaParser;

@EqualsAndHashCode(callSuper = false)
public class JMockitAnnotatedArgumentToMockito extends Recipe {
@Override
Expand Down Expand Up @@ -82,7 +83,7 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDecl
// Add mocked parameters as statements to the method declaration
if (!mockedParameter.isEmpty()) {
JavaTemplate addStatementsTemplate = JavaTemplate.builder("#{} #{} = Mockito.mock(#{}.class);\n")
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "mockito-core-3.12"))
.javaParser(getJavaParser(ctx))
.imports("org.mockito.Mockito")
.contextSensitive()
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,24 @@
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.*;
import static org.openrewrite.java.testing.jmockit.JMockitUtils.MOCKITO_ALL_IMPORT;
import static org.openrewrite.java.testing.jmockit.JMockitUtils.getJavaParser;

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 VERIFY_IN_ORDER_TEMPLATE_PREFIX_1 = "InOrder inOrder";
private static final String VERIFY_IN_ORDER_TEMPLATE_PREFIX_2 = " = inOrder(";
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 = "#{}";
private static final String ANY_TEMPLATE_FIELD = "#{any()}";
private static final String MOCKITO_IMPORT_FQN_PREFX = "org.mockito.Mockito";
private static final String IN_ORDER_IMPORT_FQN = "org.mockito.InOrder";

private static String getObjectTemplateField(String fqn) {
return "#{any(" + fqn + ")}";
Expand All @@ -53,6 +56,7 @@ private static String getObjectTemplateField(String fqn) {
private final ExecutionContext ctx;
private final J.NewClass newExpectations;
private final JMockitBlockType blockType;
private final int verificationsInOrderIdx;
// index of the Expectations block in the method body
private final int bodyStatementIndex;
private J.Block methodBody;
Expand All @@ -69,13 +73,14 @@ boolean isRewriteFailed() {
private int numStatementsAdded = 0;

JMockitBlockRewriter(JavaVisitor<ExecutionContext> visitor, ExecutionContext ctx, J.Block methodBody,
J.NewClass newExpectations, int bodyStatementIndex, JMockitBlockType blockType) {
J.NewClass newExpectations, int bodyStatementIndex, JMockitBlockType blockType, int verificationsInOrderIdx) {
this.visitor = visitor;
this.ctx = ctx;
this.methodBody = methodBody;
this.newExpectations = newExpectations;
this.bodyStatementIndex = bodyStatementIndex;
this.blockType = blockType;
this.verificationsInOrderIdx = verificationsInOrderIdx;
this.nextStatementCoordinates = newExpectations.getCoordinates().replace();
}

Expand Down Expand Up @@ -109,9 +114,8 @@ J.Block rewriteMethodBody() {
methodInvocationIdx++;
methodInvocationsToRewrite.add(new ArrayList<>());
}
if (isFullVerifications() &&
uniqueMocks.stream().noneMatch(mock -> mock.getType().equals(mockObj.getType()) &&
mock.getSimpleName().equals(mockObj.getSimpleName()))) {
if ((isFullVerifications() || isVerificationsInOrder()) &&
uniqueMocks.stream().noneMatch(mock -> mock.getSimpleName().equals(mockObj.getSimpleName()))) {
uniqueMocks.add(mockObj);
}
}
Expand All @@ -128,11 +132,16 @@ J.Block rewriteMethodBody() {
removeBlock();
}

List<Object> mocks = new ArrayList<>(uniqueMocks);
if (isVerificationsInOrder()) {
rewriteInOrderVerify(mocks);
}

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

if (isFullVerifications()) {
rewriteFullVerify(new ArrayList<>(uniqueMocks));
rewriteFullVerify(mocks);
}
return methodBody;
}
Expand All @@ -141,6 +150,10 @@ private boolean isFullVerifications() {
return this.blockType == FullVerifications;
}

private boolean isVerificationsInOrder() {
return this.blockType == VerificationsInOrder;
}

private void rewriteMethodInvocation(List<Statement> statementsToRewrite) {
final MockInvocationResults mockInvocationResults = buildMockInvocationResults(statementsToRewrite);
if (mockInvocationResults == null) {
Expand Down Expand Up @@ -223,7 +236,7 @@ private void rewriteVerify(J.MethodInvocation invocation, @Nullable Expression t
// for Verifications, replace the Verifications block
verifyCoordinates = nextStatementCoordinates;
} else {
// for Expectations put the verify at the end of the method
// for Expectations put verify at the end of the method
verifyCoordinates = methodBody.getCoordinates().lastStatement();
}
rewriteTemplate(verifyTemplate, templateParams, verifyCoordinates);
Expand All @@ -244,19 +257,38 @@ 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);
}
if (rewriteMultipleMocks(mocks, VERIFY_NO_INTERACTIONS_TEMPLATE_PREFIX)) { // verifyNoMoreInteractions(mock1, mock2 ...
visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, "verifyNoMoreInteractions", false);
}
}

private void rewriteInOrderVerify(List<Object> mocks) {
StringBuilder sb = new StringBuilder(VERIFY_IN_ORDER_TEMPLATE_PREFIX_1); // InOrder inOrder
if (verificationsInOrderIdx > 0) {
sb.append(verificationsInOrderIdx); // InOrder inOrder1
}
sb.append(VERIFY_IN_ORDER_TEMPLATE_PREFIX_2); // InOrder inOrder1 = inOrder(
if (rewriteMultipleMocks(mocks, sb.toString())) { // InOrder inOrder = inOrder(mock1, mock2 ..)
visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, "inOrder", false);
visitor.maybeAddImport(IN_ORDER_IMPORT_FQN);
}
}

private boolean rewriteMultipleMocks(List<Object> mocks, String template) {
if (mocks.isEmpty()) {
return false;
}
StringBuilder sb = new StringBuilder(template);
mocks.forEach(mock -> sb.append(ANY_TEMPLATE_FIELD).append(", "));
sb.delete(sb.length() - 2, sb.length());
sb.append(");");
rewriteTemplate(sb.toString(), mocks, nextStatementCoordinates);
if (!this.rewriteFailed) {
setNextStatementCoordinates(++numStatementsAdded);
}
return !this.rewriteFailed;
}

private void setNextStatementCoordinates(int numStatementsAdded) {
if (numStatementsAdded <= 0 && bodyStatementIndex == 0) {
nextStatementCoordinates = methodBody.getCoordinates().firstStatement();
Expand All @@ -278,14 +310,11 @@ private void rewriteTemplate(String template, List<Object> templateParams, JavaC
rewriteCoords) {
int numStatementsBefore = methodBody.getStatements().size();
methodBody = JavaTemplate.builder(template)
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "mockito-core-3.12"))
.staticImports("org.mockito.Mockito.*")
.javaParser(getJavaParser(ctx))
.staticImports(MOCKITO_ALL_IMPORT)
.imports(IN_ORDER_IMPORT_FQN)
.build()
.apply(
new Cursor(visitor.getCursor(), methodBody),
rewriteCoords,
templateParams.toArray()
);
.apply(new Cursor(visitor.getCursor(), methodBody), rewriteCoords, templateParams.toArray());
this.rewriteFailed = methodBody.getStatements().size() <= numStatementsBefore;
}

Expand Down Expand Up @@ -325,7 +354,8 @@ private void rewriteTemplate(String template, List<Object> templateParams, JavaC
return templateBuilder.toString();
}

private static void appendToTemplate(StringBuilder templateBuilder, boolean buildingResults, String templatePrefix,
private static void appendToTemplate(StringBuilder templateBuilder, boolean buildingResults, String
templatePrefix,
String templateField) {
if (!buildingResults) {
templateBuilder.append(templatePrefix);
Expand All @@ -335,8 +365,17 @@ private static void appendToTemplate(StringBuilder templateBuilder, boolean buil
templateBuilder.append(templateField);
}

private static String getVerifyTemplate(List<Expression> arguments, String verificationMode, List<Object> templateParams) {
StringBuilder templateBuilder = new StringBuilder(VERIFY_TEMPLATE_PREFIX); // eg verify(object
private String getVerifyTemplate(List<Expression> arguments, String
verificationMode, List<Object> templateParams) {
StringBuilder templateBuilder = new StringBuilder();
if (isVerificationsInOrder()) {
templateBuilder.append("inOrder");
if (this.verificationsInOrderIdx > 0) {
templateBuilder.append(this.verificationsInOrderIdx);
}
templateBuilder.append(".");
}
templateBuilder.append(VERIFY_TEMPLATE_PREFIX); // eg verify(object
if (!verificationMode.isEmpty()) {
templateBuilder.append(", ").append(verificationMode).append("(#{any(int)})"); // eg verify(object, times(2)
}
Expand Down Expand Up @@ -367,7 +406,8 @@ private static String getVerifyTemplate(List<Expression> arguments, String verif
return templateBuilder.toString();
}

private static @Nullable MockInvocationResults buildMockInvocationResults(List<Statement> expectationStatements) {
private static @Nullable MockInvocationResults buildMockInvocationResults
(List<Statement> expectationStatements) {
final MockInvocationResults resultWrapper = new MockInvocationResults();
for (int i = 1; i < expectationStatements.size(); i++) {
Statement expectationStatement = expectationStatements.get(i);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,25 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDecl
J.Block methodBody = ssr.rewriteMethodBody();
List<Statement> statements = methodBody.getStatements();

int verificationsInOrderIdx = 0;
int bodyStatementIndex = 0;
// iterate over each statement in the method body, find Expectations blocks and rewrite them
// iterate over each statement in the method body, find JMockit blocks and rewrite them
while (bodyStatementIndex < statements.size()) {
Statement s = statements.get(bodyStatementIndex);
Optional<JMockitBlockType> blockType = JMockitUtils.getJMockitBlock(s);
if (blockType.isPresent()) {
Optional<JMockitBlockType> blockTypeOpt = JMockitUtils.getJMockitBlock(s);
if (blockTypeOpt.isPresent()) {
JMockitBlockType blockType = blockTypeOpt.get();
JMockitBlockRewriter blockRewriter = new JMockitBlockRewriter(this, ctx, methodBody,
((J.NewClass) s), bodyStatementIndex, blockType.get());
((J.NewClass) s), bodyStatementIndex, blockType, verificationsInOrderIdx);
methodBody = blockRewriter.rewriteMethodBody();
statements = methodBody.getStatements();
// if the expectations rewrite failed, skip the next statement
// if the block rewrite failed, skip the next statement
if (blockRewriter.isRewriteFailed()) {
bodyStatementIndex++;
} else {
if (blockType == JMockitBlockType.VerificationsInOrder) {
verificationsInOrderIdx++;
}
}
} else {
bodyStatementIndex++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ enum JMockitBlockType {
Expectations,
NonStrictExpectations,
Verifications,
VerificationsInOrder,
FullVerifications;

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

boolean isVerifications() {
return this == Verifications || this == FullVerifications;
return this == Verifications || this == FullVerifications || this == VerificationsInOrder;
}

static String getSupportedTypesStr() {
Expand Down
Loading

0 comments on commit b10165d

Please sign in to comment.