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

Migration from Easymock to Mockito #639

Merged
merged 37 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b42f6c9
Add `EasyMockToMockito` recipe
jevanlingen Nov 6, 2024
dfb2120
WIP: Add `EasyMockVerifyToMockitoVerify` recipe
jevanlingen Nov 8, 2024
262218a
Improve EasyMockToMockitoTest
jevanlingen Nov 8, 2024
1b6b9e2
Complete EasyMockVerifyToMockitoVerify
jevanlingen Nov 8, 2024
c6f2f6f
Complete EasyMockVerifyToMockitoVerify
jevanlingen Nov 8, 2024
03247ac
Add EasyMockRunner/Mock
jevanlingen Nov 8, 2024
68f77a4
Add TestSubject/InjectMocks
jevanlingen Nov 8, 2024
7d5b9dc
Apply suggestions from code review
jevanlingen Nov 9, 2024
7170a29
Apply suggestions from code review
jevanlingen Nov 9, 2024
5996cd7
Improve `EasyMockVerifyToMockitoVerifyTest` to prove the implementati…
jevanlingen Nov 9, 2024
a6348b5
Improve `EasyMockVerifyToMockitoVerify` recipe
jevanlingen Nov 11, 2024
c794753
Improve `EasyMockVerifyToMockitoVerify` recipe
jevanlingen Nov 11, 2024
788dccf
Improve `EasyMockVerifyToMockitoVerify` recipe
jevanlingen Nov 11, 2024
559fb51
Improve `EasyMockVerifyToMockitoVerify` recipe
jevanlingen Nov 11, 2024
86125bb
Merge remote-tracking branch 'origin/454-easymock-to-mockito' into 45…
jevanlingen Nov 11, 2024
2ec8acc
Improve `EasyMockVerifyToMockitoVerifyTest`
jevanlingen Nov 11, 2024
64d3e9b
- Introduce `NoInitializationForInjectMock` and use it in mockito.yml
jevanlingen Nov 11, 2024
eee3541
Improve `NoInitializationForInjectMock` test
jevanlingen Nov 11, 2024
d6a4fb1
Improve `NoInitializationForInjectMock`
jevanlingen Nov 11, 2024
f7aa62c
Apply suggestions from code review
jevanlingen Nov 11, 2024
d25fb0c
Add support for matchers
jevanlingen Nov 11, 2024
0f7da3c
Apply suggestions from code review
jevanlingen Nov 11, 2024
5bd7ed7
Merge branch 'main' into 454-easymock-to-mockito
timtebeek Nov 18, 2024
b3e1ba8
Apply suggestions from code review
timtebeek Nov 18, 2024
1d1c6fc
Shorten RemoveExtendsEasyMockSupport
timtebeek Nov 18, 2024
569c83b
Merge branch 'main' into 454-easymock-to-mockito
timtebeek Nov 30, 2024
82f7005
Improvement
jevanlingen Dec 10, 2024
531f71c
Merge branch 'main' into 454-easymock-to-mockito
jevanlingen Dec 10, 2024
68274b2
Improvement
jevanlingen Dec 10, 2024
92d7d55
Improvement
jevanlingen Dec 10, 2024
f474b48
Improvement
jevanlingen Dec 10, 2024
1c8885d
Move easymock jar to src/test/resource/META-INF/rewrite/classpath
timtebeek Dec 10, 2024
d00bfd0
Only add Mockito dependency if using easymock first
timtebeek Dec 10, 2024
2f5463b
Tests should only have classpath entries for inputs
timtebeek Dec 10, 2024
89a6889
No need for classpathFromResources for easymock
timtebeek Dec 10, 2024
69d89ff
Minor polish
timtebeek Dec 10, 2024
02d3e6b
Remove unused import
timtebeek Dec 10, 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
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 All @@ -35,6 +36,7 @@ dependencies {
implementation("org.openrewrite:rewrite-java")
implementation("org.openrewrite:rewrite-gradle")
implementation("org.openrewrite:rewrite-maven")
implementation("org.openrewrite.recipe:rewrite-migrate-java:$rewriteVersion")
timtebeek marked this conversation as resolved.
Show resolved Hide resolved
implementation("org.openrewrite.recipe:rewrite-java-dependencies:$rewriteVersion")
implementation("org.openrewrite.recipe:rewrite-static-analysis:$rewriteVersion")
runtimeOnly("org.openrewrite:rewrite-java-17")
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()
jevanlingen marked this conversation as resolved.
Show resolved Hide resolved
.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,61 @@
/*
* 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.JavaType;
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);

maybeRemoveImport(EASYMOCK);

if (cd.getExtends() != null) {
JavaType.FullyQualified fqn = TypeUtils.asFullyQualified(cd.getExtends().getType());
if (fqn != null && fqn.isAssignableTo(EASYMOCK)) {
cd = cd.withExtends(null);
}
}
return cd;
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2021 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,59 @@
/*
* Copyright 2021 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.ExecutionContext;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
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.stream.Collectors;

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 (new AnnotationService().matches(getCursor(), INJECT_MOCKS)) {
return vd.withVariables(vd.getVariables().stream().map(var -> var.withInitializer(null)).collect(Collectors.toList()));
}

return vd;
}
});
}
}
Binary file not shown.
Binary file not shown.
Loading
Loading