diff --git a/build.gradle.kts b/build.gradle.kts index 51cef6770..b15b500dd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,6 +33,7 @@ recipeDependencies { val rewriteVersion = rewriteRecipe.rewriteVersion.get() dependencies { implementation("org.openrewrite:rewrite-java:$rewriteVersion") + implementation("org.openrewrite:rewrite-gradle:$rewriteVersion") implementation("org.openrewrite:rewrite-maven:$rewriteVersion") runtimeOnly("org.openrewrite:rewrite-java-17:$rewriteVersion") diff --git a/src/main/java/org/openrewrite/java/testing/mockito/AnyStringToNullable.java b/src/main/java/org/openrewrite/java/testing/mockito/AnyStringToNullable.java new file mode 100644 index 000000000..2d8262169 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/mockito/AnyStringToNullable.java @@ -0,0 +1,85 @@ +/* + * Copyright 2021 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.lang.Nullable; +import org.openrewrite.java.*; +import org.openrewrite.java.search.UsesMethod; +import org.openrewrite.java.tree.J; + +import java.time.Duration; + +/** + * Replace Mockito 1.x `anyString()` with `nullable(String.class)` + */ +public class AnyStringToNullable extends Recipe { + private static final MethodMatcher ANY_STRING = new MethodMatcher("org.mockito.Mockito anyString()"); + private static final String MOCKITO_CLASS_PATH = "mockito-core-3.12.4"; + private static J.MethodInvocation nullableStringMethodTemplate = null; + + @Override + public String getDisplayName() { + return "Replace Mockito 1.x `anyString()` with `nullable(String.class)`"; + } + + @Override + public String getDescription() { + return "Since Mockito 2.10 `anyString()` no longer matches null values. Use `nullable(Class)` instead."; + } + + @Override + public Duration getEstimatedEffortPerOccurrence() { + return Duration.ofMinutes(1); + } + + @Nullable + @Override + protected TreeVisitor getSingleSourceApplicableTest() { + return new UsesMethod<>(ANY_STRING); + } + + @Override + protected TreeVisitor getVisitor() { + return new JavaIsoVisitor() { + + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) { + J.MethodInvocation mi = super.visitMethodInvocation(method, executionContext); + + if (ANY_STRING.matches(mi)) { + maybeAddImport("org.mockito.ArgumentMatchers", "nullable", false); + maybeRemoveImport("org.mockito.Mockito.anyString"); + return getNullableMethodTemplate().withPrefix(mi.getPrefix()); + } + return mi; + } + }; + } + + private static J.MethodInvocation getNullableMethodTemplate() { + if (nullableStringMethodTemplate == null) { + nullableStringMethodTemplate = PartProvider.buildPart("import static org.mockito.ArgumentMatchers" + + ".nullable;\n" + + "public class A {\n" + + " void method() {\n" + + " Object x = nullable(String.class);\n" + + " }\n" + + "}", J.MethodInvocation.class, MOCKITO_CLASS_PATH); + } + return nullableStringMethodTemplate; + } +} diff --git a/src/main/resources/META-INF/rewrite/mockito.yml b/src/main/resources/META-INF/rewrite/mockito.yml index 9f6392d51..78c926874 100644 --- a/src/main/resources/META-INF/rewrite/mockito.yml +++ b/src/main/resources/META-INF/rewrite/mockito.yml @@ -39,6 +39,7 @@ recipeList: - org.openrewrite.java.ChangeType: oldFullyQualifiedTypeName: org.mockito.MockitoAnnotations.Mock newFullyQualifiedTypeName: org.mockito.Mock + - org.openrewrite.java.testing.mockito.AnyToNullable - org.openrewrite.java.ChangeType: oldFullyQualifiedTypeName: org.mockito.Matchers newFullyQualifiedTypeName: org.mockito.ArgumentMatchers @@ -121,6 +122,40 @@ recipeList: newVersion: 3.x --- type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.testing.mockito.UsesMockitoAll +displayName: Uses Mockito all from v1.x +description: Finds projects that depend on `mockito-all` through Maven or Gradle. +tags: + - testing + - mockito +recipeList: + - org.openrewrite.maven.search.FindDependency: + groupId: org.mockito + artifactId: mockito-all + - org.openrewrite.gradle.search.FindDependency: + groupId: org.mockito + artifactId: mockito-all +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.testing.mockito.AnyToNullable +displayName: Replace Mockito 1.x `any(Class)` and `anyString()` with `nullable(Class)` +description: Since Mockito 2.10 `any(Class)` and `anyString()` no longer match null values. Use `nullable(Class)` instead. +tags: + - testing + - mockito +applicability: + anySource: + - org.openrewrite.java.testing.mockito.UsesMockitoAll +recipeList: + - org.openrewrite.java.ChangeMethodName: + methodPattern: org.mockito.Mockito any(java.lang.Class) + newMethodName: nullable + - org.openrewrite.java.ChangeMethodTargetToStatic: + methodPattern: org.mockito.Mockito nullable(java.lang.Class) + fullyQualifiedTargetTypeName: org.mockito.ArgumentMatchers + - org.openrewrite.java.testing.mockito.AnyStringToNullable +--- +type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.testing.mockito.ReplacePowerMockito displayName: Replace Powermock with raw Mockito description: Replace Powermock with raw Mockito. diff --git a/src/test/java/org/openrewrite/java/testing/mockito/AnyStringToNullableTest.java b/src/test/java/org/openrewrite/java/testing/mockito/AnyStringToNullableTest.java new file mode 100755 index 000000000..a4326321d --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/mockito/AnyStringToNullableTest.java @@ -0,0 +1,107 @@ +/* + * Copyright 2021 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.junit.jupiter.api.Test; +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 AnyStringToNullableTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec + .parser(JavaParser.fromJavaVersion() + .classpathFromResources(new InMemoryExecutionContext(), "mockito-core-3.12.4") + .logCompilationWarningsAndErrors(true)) + .recipe(new AnyStringToNullable()); + } + + @Test + void replaceAnyStringWithNullableStringClass() { + //language=java + rewriteRun( + java(""" + class Example { + String greet(String name) { + return "Hello " + name; + } + } + """), + java( + """ + import static org.mockito.Mockito.anyString; + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.when; + + class MyTest { + void test() { + Example example = mock(Example.class); + when(example.greet(anyString())).thenReturn("Hello world"); + } + } + """, + """ + import static org.mockito.ArgumentMatchers.nullable; + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.when; + + class MyTest { + void test() { + Example example = mock(Example.class); + when(example.greet(nullable(String.class))).thenReturn("Hello world"); + } + } + """ + ) + ); + } + + @Test + void doNotReplaceAnyInt() { + //language=java + rewriteRun( + java(""" + class Example { + String greet(int value) { + return "Hello " + value; + } + } + """), + java( + """ + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.when; + import static org.mockito.Mockito.anyInt; + + class MyTest { + void test() { + Example example = mock(Example.class); + when(example.greet(anyInt())).thenReturn("Hello 5"); + } + } + """ + ) + ); + } + +} + + diff --git a/src/test/java/org/openrewrite/java/testing/mockito/AnyToNullableTest.java b/src/test/java/org/openrewrite/java/testing/mockito/AnyToNullableTest.java new file mode 100755 index 000000000..4cfa10442 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/mockito/AnyToNullableTest.java @@ -0,0 +1,102 @@ +/* + * Copyright 2021 the original author or authors. + *

+ * 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 + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.junit.jupiter.api.Test; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.config.Environment; +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.maven.Assertions.pomXml; + +class AnyToNullableTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec + .parser(JavaParser.fromJavaVersion() + .classpathFromResources(new InMemoryExecutionContext(), "mockito-core-3.12.4") + .logCompilationWarningsAndErrors(true)) + .recipe(Environment.builder() + .scanRuntimeClasspath("org.openrewrite.java.testing.mockito") + .build() + .activateRecipes("org.openrewrite.java.testing.mockito.AnyToNullable")); + } + + @Test + void replaceAnyClassWithNullableClass() { + //language=java + rewriteRun( + //language=xml + pomXml(""" + + 4.0.0 + com.example + foo + 1.0.0 + + + org.mockito + mockito-all + 1.10.19 + + + + """), + //language=java + java(""" + class Example { + String greet(Object obj) { + return "Hello " + obj; + } + } + """), + //language=java + java( + """ + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.when; + import static org.mockito.Mockito.any; + + class MyTest { + void test() { + Example example = mock(Example.class); + when(example.greet(any(Object.class))).thenReturn("Hello world"); + } + } + """, + """ + import static org.mockito.ArgumentMatchers.nullable; + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.when; + + class MyTest { + void test() { + Example example = mock(Example.class); + when(example.greet(nullable(Object.class))).thenReturn("Hello world"); + } + } + """ + ) + ); + } + +} + + diff --git a/src/test/java/org/openrewrite/java/testing/mockito/JunitMockitoUpgradeIntegrationTest.java b/src/test/java/org/openrewrite/java/testing/mockito/JunitMockitoUpgradeIntegrationTest.java index c5cbfcb1f..9975e07bb 100755 --- a/src/test/java/org/openrewrite/java/testing/mockito/JunitMockitoUpgradeIntegrationTest.java +++ b/src/test/java/org/openrewrite/java/testing/mockito/JunitMockitoUpgradeIntegrationTest.java @@ -141,7 +141,7 @@ static class Foo { int bar(byte[] bytes, String[] s, int i) { return 0; } boolean baz(String ... strings) { return true; } } - + public void usesMatchers() { Foo mockFoo = mock(Foo.class); when(mockFoo.bool(anyString(), anyInt(), any(Object.class))).thenReturn(true);