Skip to content

Commit

Permalink
Merge branch 'main' into ssheikin/testng-assert
Browse files Browse the repository at this point in the history
  • Loading branch information
timtebeek authored Dec 10, 2024
2 parents c8a6aa1 + 1d1b915 commit 52cbc2e
Show file tree
Hide file tree
Showing 14 changed files with 1,362 additions and 183 deletions.
2 changes: 2 additions & 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 Expand Up @@ -68,6 +69,7 @@ dependencies {
testRuntimeOnly("net.datafaker:datafaker:latest.release") {
exclude(group = "org.yaml", module = "snakeyaml")
}
testRuntimeOnly("org.easymock:easymock:latest.release")
testRuntimeOnly("org.mockito.kotlin:mockito-kotlin:latest.release")
testRuntimeOnly("org.testcontainers:testcontainers:latest.release")
testRuntimeOnly("org.testcontainers:nginx:latest.release")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* 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.easymock;

import org.openrewrite.ExecutionContext;
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.MethodMatcher;
import org.openrewrite.java.search.UsesMethod;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaCoordinates;
import org.openrewrite.java.tree.Statement;

import java.util.ArrayList;
import java.util.List;

import static java.lang.String.join;
import static java.util.Collections.nCopies;

public class EasyMockVerifyToMockitoVerify extends Recipe {

private static final MethodMatcher VERIFY_MATCHER = new MethodMatcher("org.easymock.EasyMock verify(..)", true);
private static final MethodMatcher EASY_MATCHER = new MethodMatcher("org.easymock.EasyMock expect(..)");

@Override
public String getDisplayName() {
return "Replace EasyMock `verify` calls with Mockito `verify` calls";
}

@Override
public String getDescription() {
return "Replace `EasyMock.verify(dependency)` with individual `Mockito.verify(dependency).method()` calls based on expected methods.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new UsesMethod<>(VERIFY_MATCHER), new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
J.MethodDeclaration md = super.visitMethodDeclaration(method, ctx);
if (md.getBody() == null) {
return md;
}

maybeAddImport("org.mockito.Mockito", "verify");
maybeRemoveImport("org.easymock.EasyMock.verify");

int idx = 0;
for (Statement statement : md.getBody().getStatements()) {
if (statement instanceof J.MethodInvocation) {
J.MethodInvocation m = (J.MethodInvocation) statement;
if (VERIFY_MATCHER.matches(m) && m.getArguments().size() == 1 && m.getArguments().get(0) instanceof J.Identifier) {
J.Identifier dependency = (J.Identifier) m.getArguments().get(0);
List<Statement> statementsAboveVerify = md.getBody().getStatements().subList(0, idx);
List<J.MethodInvocation> expectedCalls = getExpectedCalls(dependency, statementsAboveVerify);

for (int i = 0, expectedCallsSize = expectedCalls.size(); i < expectedCallsSize; i++) {
J.MethodInvocation expectedMethod = expectedCalls.get(i);
List<Expression> parameters = expectedMethod.getArguments();
if (parameters.size() == 1 && parameters.get(0) instanceof J.Empty) {
parameters.clear();
}
String anyArgs = join(",", nCopies(parameters.size(), "#{any()}"));
parameters.add(0, dependency);
Statement currStatement = md.getBody().getStatements().get(idx);
JavaCoordinates coordinates = i == 0 ? currStatement.getCoordinates().replace() : currStatement.getCoordinates().after();
md = JavaTemplate.builder("verify(#{any()})." + expectedMethod.getSimpleName() + "(" + anyArgs + ")")
.contextSensitive()
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "mockito-core-5"))
.staticImports("org.mockito.Mockito.verify")
.build()
.apply(updateCursor(md), coordinates, parameters.toArray());
if (i != 0) {
idx++;
}
}
}
}
idx++;
}

return md;
}

private List<J.MethodInvocation> getExpectedCalls(J.Identifier dependency, List<Statement> statementsAboveVerify) {
List<J.MethodInvocation> expectedCalls = new ArrayList<>();
for (Statement statement : statementsAboveVerify) {
if (statement instanceof J.MethodInvocation) {
J.MethodInvocation mi = (J.MethodInvocation) statement;
if (isExpectInvocation(mi, dependency)) {
expectedCalls.add((J.MethodInvocation) mi.getArguments().get(0));
} else if (isExpectAndReturnInvocation(mi, dependency)) {
expectedCalls.add((J.MethodInvocation) ((J.MethodInvocation) mi.getSelect()).getArguments().get(0));
}
}
}
return expectedCalls;
}

// match: expect(<dep>.someMethod());
private boolean isExpectInvocation(J.MethodInvocation mi, J.Identifier dependency) {
return EASY_MATCHER.matches(mi) &&
mi.getArguments().size() == 1 &&
mi.getArguments().get(0) instanceof J.MethodInvocation &&
((J.MethodInvocation) mi.getArguments().get(0)).getSelect() instanceof J.Identifier &&
dependency.getSimpleName().equals(((J.Identifier) ((J.MethodInvocation) mi.getArguments().get(0)).getSelect()).getSimpleName());
}

// match: expect(<dep>.someMethod()).andReturn();
private boolean isExpectAndReturnInvocation(J.MethodInvocation m, J.Identifier dependency) {
return EASY_MATCHER.matches(m.getSelect()) &&
m.getSelect() instanceof J.MethodInvocation &&
isExpectInvocation((J.MethodInvocation) m.getSelect(), dependency);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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.easymock;

import org.openrewrite.ExecutionContext;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.search.UsesType;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.TypeUtils;

public class RemoveExtendsEasyMockSupport extends Recipe {

private static final String EASYMOCK = "org.easymock.EasyMockSupport";

@Override
public String getDisplayName() {
return "Migrate Test classes that extend `org.easymock.EasyMockSupport` to use Mockito";
}

@Override
public String getDescription() {
return "Modify test classes by removing extends EasyMockSupport and replacing EasyMock methods with Mockito equivalents.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new UsesType<>(EASYMOCK, false), new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx);

if (cd.getExtends() != null && TypeUtils.isAssignableTo(EASYMOCK, cd.getExtends().getType())) {
maybeRemoveImport(EASYMOCK);
cd = cd.withExtends(null);
}
return cd;
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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.
*/
@NullMarked
package org.openrewrite.java.testing.easymock;

import org.jspecify.annotations.NullMarked;
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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.mockito;

import org.openrewrite.*;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.java.AnnotationMatcher;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.search.UsesType;
import org.openrewrite.java.service.AnnotationService;
import org.openrewrite.java.tree.J;

import java.util.Iterator;

public class NoInitializationForInjectMock extends Recipe {

private static final AnnotationMatcher INJECT_MOCKS = new AnnotationMatcher("@org.mockito.InjectMocks");

@Override
public String getDisplayName() {
return "Remove initialization when using `@InjectMocks`";
}

@Override
public String getDescription() {
return "Removes unnecessary initialization for fields annotated with `@InjectMocks` in Mockito tests.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new UsesType<>("org.mockito.*", false), new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations variableDeclarations, ExecutionContext ctx) {
J.VariableDeclarations vd = super.visitVariableDeclarations(variableDeclarations, ctx);

if (isField(getCursor()) && new AnnotationService().matches(getCursor(), INJECT_MOCKS)) {
return vd.withVariables(ListUtils.map(vd.getVariables(), it -> it.withInitializer(null)));
}

return vd;
}

// copied from org.openrewrite.java.search.FindFieldsOfType.isField(Cursor), should probably become part of the API
private boolean isField(Cursor cursor) {
Iterator<Object> path = cursor.getPath();
while (path.hasNext()) {
Object o = path.next();
if (o instanceof J.MethodDeclaration) {
return false;
}
if (o instanceof J.ClassDeclaration) {
return true;
}
}
return true;
}
});
}
}
Binary file not shown.
Loading

0 comments on commit 52cbc2e

Please sign in to comment.