diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..7a7ad4c70 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +insert_final_newline = true +trim_trailing_whitespace = true + +[src/test*/java/**.java] +indent_size = 4 +ij_continuation_indent_size = 2 diff --git a/.gitignore b/.gitignore index d213cf5c9..d622dc1d1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ out/ bin/ *.log .vscode/ +src/main/generated/ \ No newline at end of file diff --git a/LICENSE b/LICENSE index 8ff3b85fe..261eeb9e9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Apache License + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -178,15 +178,15 @@ Apache License APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" + boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate - comment syntax for the file formatPrefix. We also recommend that a + comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/build.gradle.kts b/build.gradle.kts index 6e03b6a63..7c8f3bcef 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,10 +20,13 @@ recipeDependencies { parserClasspath("org.mockito:mockito-all:1.10.19") parserClasspath("org.mockito:mockito-core:3.+") parserClasspath("org.jmockit:jmockit:1.49") + parserClasspath("org.jmockit:jmockit:1.22") // last version with NonStrictExpectations parserClasspath("org.mockito:mockito-junit-jupiter:3.+") parserClasspath("org.powermock:powermock-api-mockito:1.7.+") parserClasspath("org.powermock:powermock-core:1.7.+") parserClasspath("com.squareup.okhttp3:mockwebserver:4.10.0") + parserClasspath("org.springframework:spring-test:6.1.12") + parserClasspath("com.github.database-rider:rider-junit5:1.44.0") } val rewriteVersion = rewriteRecipe.rewriteVersion.get() @@ -36,6 +39,9 @@ dependencies { implementation("org.openrewrite.recipe:rewrite-static-analysis:$rewriteVersion") runtimeOnly("org.openrewrite:rewrite-java-17") + runtimeOnly("tech.picnic.error-prone-support:error-prone-contrib:latest.release:recipes") + compileOnly("org.junit.jupiter:junit-jupiter-engine:latest.release") + compileOnly("org.projectlombok:lombok:latest.release") annotationProcessor("org.projectlombok:lombok:latest.release") @@ -43,8 +49,16 @@ dependencies { testImplementation("org.openrewrite:rewrite-java-17") testImplementation("org.openrewrite:rewrite-groovy") + testImplementation("org.openrewrite:rewrite-test") + testImplementation("org.openrewrite:rewrite-kotlin:$rewriteVersion") testImplementation("org.openrewrite.gradle.tooling:model:$rewriteVersion") + annotationProcessor("org.openrewrite:rewrite-templating:${rewriteVersion}") + implementation("org.openrewrite:rewrite-templating:${rewriteVersion}") + compileOnly("com.google.errorprone:error_prone_core:2.+:with-dependencies") { + exclude("com.google.auto.service", "auto-service-annotations") + } + testRuntimeOnly("org.gradle:gradle-tooling-api:latest.release") testRuntimeOnly("com.tngtech.archunit:archunit:0.23.1") @@ -54,9 +68,7 @@ dependencies { testRuntimeOnly("net.datafaker:datafaker:latest.release") { exclude(group = "org.yaml", module = "snakeyaml") } + testRuntimeOnly("org.mockito.kotlin:mockito-kotlin:latest.release") testRuntimeOnly("org.testcontainers:testcontainers:latest.release") testRuntimeOnly("org.testcontainers:nginx:latest.release") - -// testImplementation("org.hamcrest:hamcrest:latest.release") -// testImplementation("org.assertj:assertj-core:latest.release") } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f..a4b76b953 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index acacbc504..2f2f53604 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionSha256Sum=a4b4158601f8636cdeeab09bd76afb640030bb5b144aafe261a5e8af027dc612 +distributionSha256Sum=f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6 diff --git a/gradlew b/gradlew index b740cf133..f3b75f3b0 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 7101f8e46..9b42019c7 100755 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## diff --git a/settings.gradle.kts b/settings.gradle.kts index 19421c709..991d06956 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -30,6 +30,12 @@ develocity { fileFingerprints = true } + publishing { + onlyIf { + authenticated + } + } + uploadInBackground = !isCiServer } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/AdoptAssertJDurationAssertions.java b/src/main/java/org/openrewrite/java/testing/assertj/AdoptAssertJDurationAssertions.java index ef26a84bd..b84ca6133 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/AdoptAssertJDurationAssertions.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/AdoptAssertJDurationAssertions.java @@ -32,17 +32,48 @@ import java.util.*; -public class AdoptAssertJDurationAssertions extends Recipe { - - static final String DURATION_ASSERT_HAS_LONG = "org.assertj.core.api.AbstractDurationAssert has*(long)"; +import static org.openrewrite.Preconditions.or; - static final String INTEGER_ASSERT_IS_EQUAL_TO = "org.assertj.core.api.AbstractIntegerAssert isEqualTo(..)"; - static final String INTEGER_ASSERT_IS_GREATER_THAN = "org.assertj.core.api.AbstractIntegerAssert isGreaterThan(..)"; - static final String INTEGER_ASSERT_IS_LESS_THAN = "org.assertj.core.api.AbstractIntegerAssert isLessThan(..)"; +public class AdoptAssertJDurationAssertions extends Recipe { - static final String LONG_ASSERT_IS_LESS_THAN = "org.assertj.core.api.AbstractLongAssert isLessThan(..)"; - static final String LONG_ASSERT_IS_GREATER_THAN = "org.assertj.core.api.AbstractLongAssert isGreaterThan(..)"; - static final String LONG_ASSERT_IS_EQUAL_TO = "org.assertj.core.api.AbstractLongAssert isEqualTo(..)"; + private static final String DURATION_ASSERT_HAS_LONG = "org.assertj.core.api.AbstractDurationAssert has*(long)"; + private static final String INTEGER_ASSERT_IS_EQUAL_TO = "org.assertj.core.api.AbstractIntegerAssert isEqualTo(..)"; + private static final String INTEGER_ASSERT_IS_GREATER_THAN = "org.assertj.core.api.AbstractIntegerAssert isGreaterThan(..)"; + private static final String INTEGER_ASSERT_IS_LESS_THAN = "org.assertj.core.api.AbstractIntegerAssert isLessThan(..)"; + private static final String LONG_ASSERT_IS_LESS_THAN = "org.assertj.core.api.AbstractLongAssert isLessThan(..)"; + private static final String LONG_ASSERT_IS_GREATER_THAN = "org.assertj.core.api.AbstractLongAssert isGreaterThan(..)"; + private static final String LONG_ASSERT_IS_EQUAL_TO = "org.assertj.core.api.AbstractLongAssert isEqualTo(..)"; + + private static final MethodMatcher ASSERT_THAT_MATCHER = new MethodMatcher("org.assertj.core.api.Assertions assertThat(..)"); + private static final MethodMatcher GET_NANO_MATCHER = new MethodMatcher("java.time.Duration getNano()"); + private static final MethodMatcher GET_SECONDS_MATCHER = new MethodMatcher("java.time.Duration getSeconds()"); + private static final MethodMatcher AS_MATCHER = new MethodMatcher("org.assertj.core.api.AbstractObjectAssert as(..)"); + private static final MethodMatcher TIME_UNIT_MATCHERS = new MethodMatcher(DURATION_ASSERT_HAS_LONG, true); + + private static final List IS_MATCHERS = Arrays.asList( + new MethodMatcher(INTEGER_ASSERT_IS_EQUAL_TO, true), + new MethodMatcher(INTEGER_ASSERT_IS_GREATER_THAN, true), + new MethodMatcher(INTEGER_ASSERT_IS_LESS_THAN, true), + + new MethodMatcher(LONG_ASSERT_IS_EQUAL_TO, true), + new MethodMatcher(LONG_ASSERT_IS_GREATER_THAN, true), + new MethodMatcher(LONG_ASSERT_IS_LESS_THAN, true) + ); + + private static final Map METHOD_MAP = new HashMap() {{ + put("getSeconds", "hasSeconds"); + put("getNano", "hasNanos"); + + put("hasNanos", "hasMillis"); + put("hasMillis", "hasSeconds"); + put("hasSeconds", "hasMinutes"); + put("hasMinutes", "hasHours"); + put("hasHours", "hasDays"); + + put("isGreaterThan", "isPositive"); + put("isLessThan", "isNegative"); + put("isEqualTo", "isZero"); + }}; @Override public String getDisplayName() { @@ -56,190 +87,156 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(Preconditions.or( + return Preconditions.check( + or( new UsesMethod<>(DURATION_ASSERT_HAS_LONG, true), - new UsesMethod<>(INTEGER_ASSERT_IS_EQUAL_TO, true), new UsesMethod<>(INTEGER_ASSERT_IS_GREATER_THAN, true), new UsesMethod<>(INTEGER_ASSERT_IS_LESS_THAN, true), - new UsesMethod<>(LONG_ASSERT_IS_EQUAL_TO, true), new UsesMethod<>(LONG_ASSERT_IS_GREATER_THAN, true), new UsesMethod<>(LONG_ASSERT_IS_LESS_THAN, true) - ), new AdoptAssertJDurationAssertionsVisitor() - ); - } - - @SuppressWarnings("DataFlowIssue") - private static class AdoptAssertJDurationAssertionsVisitor extends JavaIsoVisitor { - private static final MethodMatcher ASSERT_THAT_MATCHER = new MethodMatcher("org.assertj.core.api.Assertions assertThat(..)"); - private static final MethodMatcher GET_NANO_MATCHER = new MethodMatcher("java.time.Duration getNano()"); - private static final MethodMatcher GET_SECONDS_MATCHER = new MethodMatcher("java.time.Duration getSeconds()"); - private static final MethodMatcher AS_MATCHER = new MethodMatcher("org.assertj.core.api.AbstractObjectAssert as(..)"); - private static final MethodMatcher TIME_UNIT_MATCHERS = new MethodMatcher(DURATION_ASSERT_HAS_LONG, true); - private static final List IS_MATCHERS = Arrays.asList( - new MethodMatcher(INTEGER_ASSERT_IS_EQUAL_TO, true), - new MethodMatcher(INTEGER_ASSERT_IS_GREATER_THAN, true), - new MethodMatcher(INTEGER_ASSERT_IS_LESS_THAN, true), - - new MethodMatcher(LONG_ASSERT_IS_EQUAL_TO, true), - new MethodMatcher(LONG_ASSERT_IS_GREATER_THAN, true), - new MethodMatcher(LONG_ASSERT_IS_LESS_THAN, true) - ); - private static final Map METHOD_MAP = new HashMap() {{ - put("getSeconds", "hasSeconds"); - put("getNano", "hasNanos"); - - put("hasNanos", "hasMillis"); - put("hasMillis", "hasSeconds"); - put("hasSeconds", "hasMinutes"); - put("hasMinutes", "hasHours"); - put("hasHours", "hasDays"); - - put("isGreaterThan", "isPositive"); - put("isLessThan", "isNegative"); - put("isEqualTo", "isZero"); - }}; - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); - if (TIME_UNIT_MATCHERS.matches(mi)) { - return simplifyTimeUnits(mi, ctx); - } else if (IS_MATCHERS.stream().anyMatch(matcher -> matcher.matches(mi))) { - return simplifyMultipleAssertions(mi, ctx); - } - return mi; - } - - private J.MethodInvocation simplifyMultipleAssertions(J.MethodInvocation m, ExecutionContext ctx) { - Expression isEqualToArg = m.getArguments().get(0); - Expression select = m.getSelect(); - List templateParameters = new ArrayList<>(); - templateParameters.add(null); - Expression asDescription = null; - - if (AS_MATCHER.matches(select)) { - asDescription = ((J.MethodInvocation) select).getArguments().get(0); - select = ((J.MethodInvocation) select).getSelect(); - templateParameters.add(asDescription); - } - - if (!ASSERT_THAT_MATCHER.matches(select)) { - return m; - } - - Expression assertThatArgumentExpr = ((J.MethodInvocation) select).getArguments().get(0); - if (!(assertThatArgumentExpr instanceof J.MethodInvocation)) { - return m; - } - J.MethodInvocation assertThatArg = (J.MethodInvocation) assertThatArgumentExpr; - - if (isZero(isEqualToArg) && checkIfRelatedToDuration(assertThatArg)) { - String formatted_template = formatTemplate("assertThat(#{any()}).%s();", m.getSimpleName(), asDescription); - templateParameters.set(0, assertThatArg); - return applyTemplate(ctx, m, formatted_template, templateParameters.toArray()); - } - - if (GET_NANO_MATCHER.matches(assertThatArg) || GET_SECONDS_MATCHER.matches(assertThatArg)) { - Expression assertThatArgSelect = assertThatArg.getSelect(); - String methodName = assertThatArg.getSimpleName(); - String formatted_template = formatTemplate("assertThat(#{any()}).%s(#{any()});", methodName, asDescription); - templateParameters.set(0, assertThatArgSelect); - templateParameters.add(isEqualToArg); - - return applyTemplate(ctx, m, formatted_template, templateParameters.toArray()); - } - - return m; - } - - private boolean isZero(Expression isEqualToArg) { - if (isEqualToArg instanceof J.Literal) { - J.Literal literal = (J.Literal) isEqualToArg; - return literal.getValue() instanceof Number && ((Number) literal.getValue()).longValue() == 0; - } - return false; - } - - private J.MethodInvocation simplifyTimeUnits(J.MethodInvocation m, ExecutionContext ctx) { - Expression arg = m.getArguments().get(0); - Long argValue = SimplifyDurationCreationUnits.getConstantIntegralValue(arg); - if (argValue == null) { - return m; - } - - List unitInfo = getUnitInfo(m.getSimpleName(), Math.toIntExact(argValue)); - String methodName = (String) unitInfo.get(0); - int methodArg = (int) unitInfo.get(1); - if (!(m.getSimpleName().equals(methodName))) { - // update method invocation with new name and arg - String template = String.format("#{any()}.%s(%d)", methodName, methodArg); - return applyTemplate(ctx, m, template, m.getSelect()); - } - - return m; - } - - private static List getUnitInfo(String name, int argValue) { - final int timeLength; - if (name.equals("hasSeconds") || name.equals("hasMinutes")) { - timeLength = 60; - } else if (name.equals("hasNanos") || name.equals("hasMillis")) { - timeLength = 1000; - } else if (name.equals("hasHours")) { - timeLength = 24; - } else { - return Arrays.asList(name, argValue); - } - - if (argValue % timeLength == 0) { - String newName = METHOD_MAP.get(name); - return getUnitInfo(newName, argValue / timeLength); - } else { - // returning name, newArg - return Arrays.asList(name, argValue); - } - } - - private J.MethodInvocation applyTemplate(ExecutionContext ctx, J.MethodInvocation m, String template, Object... parameters) { - J.MethodInvocation invocation = JavaTemplate.builder(template) - .contextSensitive() - .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) - .build() - .apply(getCursor(), m.getCoordinates().replace(), parameters); - - // retain whitespace formatting - if (invocation.getPadding().getSelect() != null && m.getPadding().getSelect() != null) { - return invocation.getPadding() - .withSelect( - invocation.getPadding().getSelect() - .withAfter(m.getPadding().getSelect().getAfter()) - ); - } - return invocation; - } - - private boolean checkIfRelatedToDuration(J.MethodInvocation argument) { - // assertThat(.).isEqual(0) - if (argument.getSelect() != null) { - if (argument.getSelect() instanceof J.MethodInvocation) { - J.MethodInvocation selectMethod = (J.MethodInvocation) argument.getSelect(); - return TypeUtils.isOfType(selectMethod.getType(), JavaType.buildType("java.time.Duration")); + ), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (TIME_UNIT_MATCHERS.matches(mi)) { + return simplifyTimeUnits(mi, ctx); + } else if (IS_MATCHERS.stream().anyMatch(matcher -> matcher.matches(mi))) { + return simplifyMultipleAssertions(mi, ctx); + } + return mi; + } + + private J.MethodInvocation simplifyMultipleAssertions(J.MethodInvocation m, ExecutionContext ctx) { + Expression isEqualToArg = m.getArguments().get(0); + Expression select = m.getSelect(); + List templateParameters = new ArrayList<>(); + templateParameters.add(null); + Expression asDescription = null; + + if (AS_MATCHER.matches(select)) { + asDescription = ((J.MethodInvocation) select).getArguments().get(0); + select = ((J.MethodInvocation) select).getSelect(); + templateParameters.add(asDescription); + } + + if (!ASSERT_THAT_MATCHER.matches(select)) { + return m; + } + + Expression assertThatArgumentExpr = ((J.MethodInvocation) select).getArguments().get(0); + if (!(assertThatArgumentExpr instanceof J.MethodInvocation)) { + return m; + } + J.MethodInvocation assertThatArg = (J.MethodInvocation) assertThatArgumentExpr; + + if (isZero(isEqualToArg) && checkIfRelatedToDuration(assertThatArg)) { + String formatted_template = formatTemplate("assertThat(#{any()}).%s();", m.getSimpleName(), asDescription); + templateParameters.set(0, assertThatArg); + return applyTemplate(ctx, m, formatted_template, templateParameters.toArray()); + } + + if (GET_NANO_MATCHER.matches(assertThatArg) || GET_SECONDS_MATCHER.matches(assertThatArg)) { + Expression assertThatArgSelect = assertThatArg.getSelect(); + String methodName = assertThatArg.getSimpleName(); + String formatted_template = formatTemplate("assertThat(#{any()}).%s(#{any()});", methodName, asDescription); + templateParameters.set(0, assertThatArgSelect); + templateParameters.add(isEqualToArg); + + return applyTemplate(ctx, m, formatted_template, templateParameters.toArray()); + } + + return m; + } + + private boolean isZero(Expression isEqualToArg) { + if (isEqualToArg instanceof J.Literal) { + J.Literal literal = (J.Literal) isEqualToArg; + return literal.getValue() instanceof Number && ((Number) literal.getValue()).longValue() == 0; + } + return false; + } + + private J.MethodInvocation simplifyTimeUnits(J.MethodInvocation m, ExecutionContext ctx) { + Expression arg = m.getArguments().get(0); + Long argValue = SimplifyDurationCreationUnits.getConstantIntegralValue(arg); + if (argValue == null) { + return m; + } + + List unitInfo = getUnitInfo(m.getSimpleName(), Math.toIntExact(argValue)); + String methodName = (String) unitInfo.get(0); + int methodArg = (int) unitInfo.get(1); + if (!(m.getSimpleName().equals(methodName))) { + // update method invocation with new name and arg + String template = String.format("#{any()}.%s(%d)", methodName, methodArg); + return applyTemplate(ctx, m, template, m.getSelect()); + } + + return m; + } + + private List getUnitInfo(String name, int argValue) { + final int timeLength; + if (name.equals("hasSeconds") || name.equals("hasMinutes")) { + timeLength = 60; + } else if (name.equals("hasNanos") || name.equals("hasMillis")) { + timeLength = 1000; + } else if (name.equals("hasHours")) { + timeLength = 24; + } else { + return Arrays.asList(name, argValue); + } + + if (argValue % timeLength == 0) { + String newName = METHOD_MAP.get(name); + return getUnitInfo(newName, argValue / timeLength); + } else { + // returning name, newArg + return Arrays.asList(name, argValue); + } + } + + private J.MethodInvocation applyTemplate(ExecutionContext ctx, J.MethodInvocation m, String template, Object... parameters) { + J.MethodInvocation invocation = JavaTemplate.builder(template) + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), m.getCoordinates().replace(), parameters); + + // retain whitespace formatting + if (invocation.getPadding().getSelect() != null && m.getPadding().getSelect() != null) { + return invocation.getPadding() + .withSelect( + invocation.getPadding().getSelect() + .withAfter(m.getPadding().getSelect().getAfter()) + ); + } + return invocation; + } + + private boolean checkIfRelatedToDuration(J.MethodInvocation argument) { + if (argument.getSelect() != null) { + if (argument.getSelect() instanceof J.MethodInvocation) { + J.MethodInvocation selectMethod = (J.MethodInvocation) argument.getSelect(); + return TypeUtils.isOfType(selectMethod.getType(), JavaType.buildType("java.time.Duration")); + } + } + return false; + } + + @SuppressWarnings("ConstantValue") + private String formatTemplate(String template, String methodName, Object asDescriptionArg) { + String replacementMethod = METHOD_MAP.get(methodName); + if (asDescriptionArg == null) { + return String.format(template, replacementMethod); + } + StringBuilder newTemplate = new StringBuilder(template); + newTemplate.insert(newTemplate.indexOf(").") + 1, ".as(#{any()})"); + return String.format(newTemplate.toString(), replacementMethod); + } } - } - return false; - } - - @SuppressWarnings("ConstantValue") - private String formatTemplate(String template, String methodName, Object asDescriptionArg) { - String replacementMethod = METHOD_MAP.get(methodName); - if (asDescriptionArg == null) { - return String.format(template, replacementMethod); - } - StringBuilder newTemplate = new StringBuilder(template); - newTemplate.insert(newTemplate.indexOf(").") + 1, ".as(#{any()})"); - return String.format(newTemplate.toString(), replacementMethod); - } + ); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/CollapseConsecutiveAssertThatStatements.java b/src/main/java/org/openrewrite/java/testing/assertj/CollapseConsecutiveAssertThatStatements.java new file mode 100644 index 000000000..478474ef8 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/assertj/CollapseConsecutiveAssertThatStatements.java @@ -0,0 +1,130 @@ +/* + * Copyright 2024 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.assertj; + +import org.openrewrite.*; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.search.SemanticallyEqual; +import org.openrewrite.java.search.UsesMethod; +import org.openrewrite.java.tree.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Incubating(since = "2.17.0") +public class CollapseConsecutiveAssertThatStatements extends Recipe { + private static final MethodMatcher ASSERT_THAT = new MethodMatcher("org.assertj.core.api.Assertions assertThat(..)"); + + @Override + public String getDisplayName() { + return "Collapse consecutive `assertThat` statements"; + } + + @Override + public String getDescription() { + return "Collapse consecutive `assertThat` statements into single `assertThat` chained statement. This recipe ignores `assertThat` statements that have method invocation as parameter."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesMethod<>(ASSERT_THAT), new JavaIsoVisitor() { + @Override + public J.Block visitBlock(J.Block block, ExecutionContext ctx) { + J.Block bl = super.visitBlock(block, ctx); + + List statementsCollapsed = new ArrayList<>(); + for (List group : getGroupedStatements(bl)) { + if (group.size() <= 1) { + statementsCollapsed.addAll(group); + } else { + statementsCollapsed.add(getCollapsedAssertThat(group)); + } + } + + return bl.withStatements(statementsCollapsed); + } + + private List> getGroupedStatements(J.Block bl) { + List originalStatements = bl.getStatements(); + List> groupedStatements = new ArrayList<>(); + Expression currentActual = null; // The actual argument of the current group of assertThat statements + List currentGroup = new ArrayList<>(); + for (Statement statement : originalStatements) { + if (statement instanceof J.MethodInvocation) { + J.MethodInvocation assertion = (J.MethodInvocation) statement; + if (isGroupableAssertion(assertion)) { + J.MethodInvocation assertThat = (J.MethodInvocation) assertion.getSelect(); + assert assertThat != null; + Expression actual = assertThat.getArguments().get(0); + if (currentActual == null || !SemanticallyEqual.areEqual(currentActual, actual)) { + // Conclude the previous group + groupedStatements.add(currentGroup); + currentGroup = new ArrayList<>(); + currentActual = actual; + } + currentGroup.add(statement); + continue; + } + } + + // Conclude the previous group, and start a new group + groupedStatements.add(currentGroup); + currentGroup = new ArrayList<>(); + currentActual = null; + // The current statement should not be grouped with any other statement + groupedStatements.add(Collections.singletonList(statement)); + } + if (!currentGroup.isEmpty()) { + // Conclude the last group + groupedStatements.add(currentGroup); + } + return groupedStatements; + } + + private boolean isGroupableAssertion(J.MethodInvocation assertion) { + // Only match method invocations where the select is an assertThat, containing a non-method call argument + if (ASSERT_THAT.matches(assertion.getSelect())) { + J.MethodInvocation assertThat = (J.MethodInvocation) assertion.getSelect(); + if (assertThat != null && !(assertThat.getArguments().get(0) instanceof MethodCall)) { + return TypeUtils.isOfType(assertThat.getType(), assertion.getType()); + } + } + return false; + } + + private J.MethodInvocation getCollapsedAssertThat(List consecutiveAssertThatStatement) { + assert !consecutiveAssertThatStatement.isEmpty(); + Space originalPrefix = consecutiveAssertThatStatement.get(0).getPrefix(); + String continuationIndent = originalPrefix.getIndent().contains("\t") ? "\t\t" : " "; + Space indentedNewline = Space.format(originalPrefix.getLastWhitespace().replaceAll("^\\s+\n", "\n") + + continuationIndent); + J.MethodInvocation collapsed = null; + for (Statement st : consecutiveAssertThatStatement) { + J.MethodInvocation assertion = (J.MethodInvocation) st; + J.MethodInvocation assertThat = (J.MethodInvocation) assertion.getSelect(); + assert assertThat != null; + J.MethodInvocation newSelect = collapsed == null ? assertThat : collapsed; + collapsed = assertion.getPadding().withSelect(JRightPadded + .build((Expression) newSelect.withPrefix(Space.EMPTY)) + .withAfter(indentedNewline)); + } + return collapsed.withPrefix(originalPrefix); + } + }); + } +} diff --git a/src/main/java/org/openrewrite/java/testing/assertj/IsEqualToEmptyString.java b/src/main/java/org/openrewrite/java/testing/assertj/IsEqualToEmptyString.java deleted file mode 100644 index 168c14324..000000000 --- a/src/main/java/org/openrewrite/java/testing/assertj/IsEqualToEmptyString.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2024 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.assertj; - -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.MethodMatcher; -import org.openrewrite.java.search.UsesMethod; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaType; - -import java.util.Collections; - -public class IsEqualToEmptyString extends Recipe { - - private static final MethodMatcher IS_EQUAL_TO = new MethodMatcher("org.assertj.core.api.AbstractStringAssert isEqualTo(java.lang.String)"); - - @Override - public String getDisplayName() { - return "Convert `assertThat(String).isEqualTo(\"\")` to `isEmpty()`"; - } - - @Override - public String getDescription() { - return "Adopt idiomatic AssertJ assertion for empty Strings."; - } - - @Override - public TreeVisitor getVisitor() { - return Preconditions.check( - new UsesMethod<>(IS_EQUAL_TO), - new JavaIsoVisitor() { - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); - if (IS_EQUAL_TO.matches(mi) && J.Literal.isLiteralValue(mi.getArguments().get(0), "")) { - JavaType.Method isEmptyMethodType = mi.getMethodType().withName("isEmpty"); - return mi - .withName(mi.getName().withSimpleName("isEmpty").withType(isEmptyMethodType)) - .withMethodType(isEmptyMethodType) - .withArguments(Collections.emptyList()); - } - return mi; - } - } - ); - } -} diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertArrayEqualsToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertArrayEqualsToAssertThat.java index ecdde2479..2c636e98f 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertArrayEqualsToAssertThat.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertArrayEqualsToAssertThat.java @@ -23,7 +23,7 @@ import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; @@ -32,11 +32,14 @@ import java.util.List; public class JUnitAssertArrayEqualsToAssertThat extends Recipe { - private static final String JUNIT_QUALIFIED_ASSERTIONS_CLASS_NAME = "org.junit.jupiter.api.Assertions"; + + private static final String JUNIT = "org.junit.jupiter.api.Assertions"; + private static final String ASSERTJ = "org.assertj.core.api.Assertions"; + private static final MethodMatcher ASSERT_ARRAY_EQUALS_MATCHER = new MethodMatcher(JUNIT + " assertArrayEquals(..)", true); @Override public String getDisplayName() { - return "JUnit `assertArrayEquals` To AssertJ"; + return "JUnit `assertArrayEquals` to assertJ"; } @Override @@ -46,93 +49,72 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>(JUNIT_QUALIFIED_ASSERTIONS_CLASS_NAME, false), new AssertArrayEqualsToAssertThatVisitor()); - } - - public static class AssertArrayEqualsToAssertThatVisitor extends JavaIsoVisitor { - private static final MethodMatcher JUNIT_ASSERT_EQUALS = new MethodMatcher(JUNIT_QUALIFIED_ASSERTIONS_CLASS_NAME + " assertArrayEquals(..)"); - - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (!JUNIT_ASSERT_EQUALS.matches(method)) { - return method; - } - - List args = method.getArguments(); - Expression expected = args.get(0); - Expression actual = args.get(1); - - // Make sure there is a static import for "org.assertj.core.api.Assertions.assertThat" (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); - maybeRemoveImport(JUNIT_QUALIFIED_ASSERTIONS_CLASS_NAME); - - if (args.size() == 2) { - return JavaTemplate.builder("assertThat(#{anyArray()}).containsExactly(#{anyArray()});") - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply(getCursor(), method.getCoordinates().replace(), actual, expected); - } else if (args.size() == 3 && !isFloatingPointType(args.get(2))) { - Expression message = args.get(2); - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{anyArray()}).as(#{any(String)}).containsExactly(#{anyArray()});") : - JavaTemplate.builder("assertThat(#{anyArray()}).as(#{any(java.util.function.Supplier)}).containsExactly(#{anyArray()});"); - return template - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply(getCursor(), method.getCoordinates().replace(), actual, message, expected); - } else if (args.size() == 3) { - maybeAddImport("org.assertj.core.api.Assertions", "within", false); - // assert is using floating points with a delta and no message. - return JavaTemplate.builder("assertThat(#{anyArray()}).containsExactly(#{anyArray()}, within(#{any()}));") - .staticImports("org.assertj.core.api.Assertions.assertThat", "org.assertj.core.api.Assertions.within") - .javaParser(assertionsParser(ctx)) + return Preconditions.check(new UsesMethod<>(ASSERT_ARRAY_EQUALS_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation md = super.visitMethodInvocation(method, ctx); + if (!ASSERT_ARRAY_EQUALS_MATCHER.matches(md)) { + return md; + } + + maybeAddImport(ASSERTJ, "assertThat", false); + maybeRemoveImport(JUNIT); + + List args = md.getArguments(); + Expression expected = args.get(0); + Expression actual = args.get(1); + if (args.size() == 2) { + return JavaTemplate.builder("assertThat(#{anyArray()}).containsExactly(#{anyArray()});") + .staticImports(ASSERTJ + ".assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), md.getCoordinates().replace(), actual, expected); + } + if (args.size() == 3 && isFloatingPointType(args.get(2))) { + maybeAddImport(ASSERTJ, "within", false); + // assert is using floating points with a delta and no message. + return JavaTemplate.builder("assertThat(#{anyArray()}).containsExactly(#{anyArray()}, within(#{any()}));") + .staticImports(ASSERTJ + ".assertThat", ASSERTJ + ".within") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), md.getCoordinates().replace(), actual, expected, args.get(2)); + } + if (args.size() == 3) { + Expression message = args.get(2); + return JavaTemplate.builder("assertThat(#{anyArray()}).as(#{any()}).containsExactly(#{anyArray()});") + .staticImports(ASSERTJ + ".assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), md.getCoordinates().replace(), actual, message, expected); + } + + maybeAddImport(ASSERTJ, "within", false); + + // The assertEquals is using a floating point with a delta argument and a message. + Expression message = args.get(3); + return JavaTemplate.builder("assertThat(#{anyArray()}).as(#{any()}).containsExactly(#{anyArray()}, within(#{}));") + .staticImports(ASSERTJ + ".assertThat", ASSERTJ + ".within") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply(getCursor(), method.getCoordinates().replace(), actual, expected, args.get(2)); + .apply(getCursor(), md.getCoordinates().replace(), actual, message, expected, args.get(2)); } - // The assertEquals is using a floating point with a delta argument and a message. - Expression message = args.get(3); - maybeAddImport("org.assertj.core.api.Assertions", "within", false); - - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{anyArray()}).as(#{any(String)}).containsExactly(#{anyArray()}, within(#{any()}));") : - JavaTemplate.builder("assertThat(#{anyArray()}).as(#{any(java.util.function.Supplier)}).containsExactly(#{anyArray()}, within(#{}));"); - return template - .staticImports("org.assertj.core.api.Assertions.assertThat", "org.assertj.core.api.Assertions.within") - .javaParser(assertionsParser(ctx)) - .build() - .apply(getCursor(), method.getCoordinates().replace(), actual, message, expected, args.get(2)); - } - - /** - * Returns true if the expression's type is either a primitive float/double or their object forms Float/Double - * - * @param expression The expression parsed from the original AST. - * @return true if the type is a floating point number. - */ - private static boolean isFloatingPointType(Expression expression) { - - JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(expression.getType()); - if (fullyQualified != null) { - String typeName = fullyQualified.getFullyQualifiedName(); - return "java.lang.Double".equals(typeName) || "java.lang.Float".equals(typeName); + /** + * Returns true if the expression's type is either a primitive float/double or their object forms Float/Double + * + * @param expression The expression parsed from the original AST. + * @return true if the type is a floating point number. + */ + private boolean isFloatingPointType(Expression expression) { + JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(expression.getType()); + if (fullyQualified != null) { + String typeName = fullyQualified.getFullyQualifiedName(); + return "java.lang.Double".equals(typeName) || "java.lang.Float".equals(typeName); + } + + JavaType.Primitive parameterType = TypeUtils.asPrimitive(expression.getType()); + return parameterType == JavaType.Primitive.Double || parameterType == JavaType.Primitive.Float; } - - JavaType.Primitive parameterType = TypeUtils.asPrimitive(expression.getType()); - return parameterType == JavaType.Primitive.Double || parameterType == JavaType.Primitive.Float; - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertEqualsToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertEqualsToAssertThat.java index 276c3870d..cbfad48c2 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertEqualsToAssertThat.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertEqualsToAssertThat.java @@ -23,7 +23,7 @@ import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; @@ -33,6 +33,10 @@ public class JUnitAssertEqualsToAssertThat extends Recipe { + private static final String JUNIT = "org.junit.jupiter.api.Assertions"; + private static final String ASSERTJ = "org.assertj.core.api.Assertions"; + private static final MethodMatcher ASSERT_EQUALS_MATCHER = new MethodMatcher(JUNIT + " assertEquals(..)", true); + @Override public String getDisplayName() { return "JUnit `assertEquals` to AssertJ"; @@ -45,105 +49,67 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", false), new AssertEqualsToAssertThatVisitor()); - } - - public static class AssertEqualsToAssertThatVisitor extends JavaIsoVisitor { - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - private static final MethodMatcher JUNIT_ASSERT_EQUALS = new MethodMatcher("org.junit.jupiter.api.Assertions" + " assertEquals(..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (!JUNIT_ASSERT_EQUALS.matches(method)) { - return method; - } - - List args = method.getArguments(); - Expression expected = args.get(0); - Expression actual = args.get(1); - - //always add the import (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); - - // Remove import for "org.junit.jupiter.api.Assertions" if no longer used. - maybeRemoveImport("org.junit.jupiter.api.Assertions"); - - if (args.size() == 2) { - return JavaTemplate.builder("assertThat(#{any()}).isEqualTo(#{any()});") - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply(getCursor(), method.getCoordinates().replace(), actual, expected); - } else if (args.size() == 3 && !isFloatingPointType(args.get(2))) { - Expression message = args.get(2); - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isEqualTo(#{any()});") : - JavaTemplate.builder("assertThat(#{any()}).as(#{any(java.util.function.Supplier)}).isEqualTo(#{any()});"); - return template - .staticImports("org.assertj.core.api.Assertions.assertThat") + return Preconditions.check(new UsesMethod<>(ASSERT_EQUALS_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (!ASSERT_EQUALS_MATCHER.matches(mi)) { + return mi; + } + + maybeAddImport(ASSERTJ, "assertThat", false); + maybeRemoveImport(JUNIT); + + List args = mi.getArguments(); + Expression expected = args.get(0); + Expression actual = args.get(1); + if (args.size() == 2) { + return JavaTemplate.builder("assertThat(#{any()}).isEqualTo(#{any()});") + .staticImports(ASSERTJ + ".assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual, expected); + } + if (args.size() == 3 && !isFloatingPointType(args.get(2))) { + Expression message = args.get(2); + return JavaTemplate.builder("assertThat(#{any()}).as(#{any()}).isEqualTo(#{any()});") + .staticImports(ASSERTJ + ".assertThat") + .imports("java.util.function.Supplier") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual, message, expected); + } + if (args.size() == 3) { + maybeAddImport(ASSERTJ, "within", false); + return JavaTemplate.builder("assertThat(#{any()}).isCloseTo(#{any()}, within(#{any()}));") + .staticImports(ASSERTJ + ".assertThat", ASSERTJ + ".within") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual, expected, args.get(2)); + } + + maybeAddImport(ASSERTJ, "within", false); + + // The assertEquals is using a floating point with a delta argument and a message. + Expression message = args.get(3); + return JavaTemplate.builder("assertThat(#{any()}).as(#{any()}).isCloseTo(#{any()}, within(#{any()}));") + .staticImports(ASSERTJ + ".assertThat", ASSERTJ + ".within") .imports("java.util.function.Supplier") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - message, - expected - ); - } else if (args.size() == 3) { - //always add the import (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "within", false); - return JavaTemplate.builder("assertThat(#{any()}).isCloseTo(#{any()}, within(#{any()}));") - .staticImports("org.assertj.core.api.Assertions.assertThat", "org.assertj.core.api.Assertions.within") - .javaParser(assertionsParser(ctx)) + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply(getCursor(), method.getCoordinates().replace(), actual, expected, args.get(2)); - + .apply(getCursor(), mi.getCoordinates().replace(), actual, message, expected, args.get(2)); } - // The assertEquals is using a floating point with a delta argument and a message. - Expression message = args.get(3); - - //always add the import (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "within", false); - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isCloseTo(#{any()}, within(#{any()}));") : - JavaTemplate.builder("assertThat(#{any()}).as(#{any(java.util.function.Supplier)}).isCloseTo(#{any()}, within(#{any()}));"); - return template - .staticImports("org.assertj.core.api.Assertions.assertThat", "org.assertj.core.api.Assertions.within") - .imports("java.util.function.Supplier") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - message, - expected, - args.get(2) - ); - } + private boolean isFloatingPointType(Expression expression) { + JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(expression.getType()); + if (fullyQualified != null) { + String typeName = fullyQualified.getFullyQualifiedName(); + return "java.lang.Double".equals(typeName) || "java.lang.Float".equals(typeName); + } - private static boolean isFloatingPointType(Expression expression) { - - JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(expression.getType()); - if (fullyQualified != null) { - String typeName = fullyQualified.getFullyQualifiedName(); - return "java.lang.Double".equals(typeName) || "java.lang.Float".equals(typeName); + JavaType.Primitive parameterType = TypeUtils.asPrimitive(expression.getType()); + return parameterType == JavaType.Primitive.Double || parameterType == JavaType.Primitive.Float; } - - JavaType.Primitive parameterType = TypeUtils.asPrimitive(expression.getType()); - return parameterType == JavaType.Primitive.Double || parameterType == JavaType.Primitive.Float; - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertFalseToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertFalseToAssertThat.java index 149e5b3df..84d1462a1 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertFalseToAssertThat.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertFalseToAssertThat.java @@ -23,15 +23,16 @@ import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.TypeUtils; import java.util.List; public class JUnitAssertFalseToAssertThat extends Recipe { + private static final MethodMatcher ASSERT_FALSE_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertFalse(boolean, ..)", true); + @Override public String getDisplayName() { return "JUnit `assertFalse` to AssertJ"; @@ -44,66 +45,34 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", false), new AssertFalseToAssertThatVisitor()); - } - - public static class AssertFalseToAssertThatVisitor extends JavaIsoVisitor { - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - private static final MethodMatcher JUNIT_ASSERT_FALSE = new MethodMatcher("org.junit.jupiter.api.Assertions" + " assertFalse(boolean, ..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (!JUNIT_ASSERT_FALSE.matches(method)) { - return method; - } + return Preconditions.check(new UsesMethod<>(ASSERT_FALSE_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (!ASSERT_FALSE_MATCHER.matches(mi)) { + return mi; + } + + maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); + maybeRemoveImport("org.junit.jupiter.api.Assertions"); + + List args = mi.getArguments(); + Expression actual = args.get(0); + if (args.size() == 1) { + return JavaTemplate.builder("assertThat(#{any(boolean)}).isFalse();") + .staticImports("org.assertj.core.api.Assertions.assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual); + } - List args = method.getArguments(); - Expression actual = args.get(0); - - if (args.size() == 1) { - method = JavaTemplate.builder("assertThat(#{any(boolean)}).isFalse();") - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual - ); - } else { Expression message = args.get(1); - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{any(boolean)}).as(#{any(String)}).isFalse();") : - JavaTemplate.builder("assertThat(#{any(boolean)}).as(#{any(java.util.function.Supplier)}).isFalse();"); - - method = template + return JavaTemplate.builder("assertThat(#{any(boolean)}).as(#{any()}).isFalse();") .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - message - ); + .apply(getCursor(), mi.getCoordinates().replace(), actual, message); } - - //Make sure there is a static import for "org.assertj.core.api.Assertions.assertThat" (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); - - // Remove import for "org.junit.jupiter.api.Assertions" if no longer used. - maybeRemoveImport("org.junit.jupiter.api.Assertions"); - - return method; - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThat.java new file mode 100644 index 000000000..f5921f80c --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThat.java @@ -0,0 +1,76 @@ +/* + * Copyright 2024 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.assertj; + +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; + +public class JUnitAssertInstanceOfToAssertThat extends Recipe { + + private static final MethodMatcher ASSERT_INSTANCE_OF_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertInstanceOf(..)", true); + + @Override + public String getDisplayName() { + return "JUnit `assertInstanceOf` to AssertJ"; + } + + @Override + public String getDescription() { + return "Convert JUnit-style `assertInstanceOf()` to AssertJ's `assertThat().isInstanceOf()`."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesMethod<>(ASSERT_INSTANCE_OF_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (!ASSERT_INSTANCE_OF_MATCHER.matches(mi)) { + return mi; + } + + maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); + maybeRemoveImport("org.junit.jupiter.api.Assertions"); + + Expression expected = mi.getArguments().get(0); + Expression actual = mi.getArguments().get(1); + if (mi.getArguments().size() == 2) { + return JavaTemplate.builder("assertThat(#{any()}).isInstanceOf(#{any()});") + .staticImports("org.assertj.core.api.Assertions.assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), method.getCoordinates().replace(), actual, expected); + } + + Expression messageOrSupplier = mi.getArguments().get(2); + return JavaTemplate.builder("assertThat(#{any()}).as(#{any()}).isInstanceOf(#{any()});") + .staticImports("org.assertj.core.api.Assertions.assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), method.getCoordinates().replace(), actual, messageOrSupplier, expected); + } + }); + } +} diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNotEqualsToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNotEqualsToAssertThat.java index d78506810..f8dfce9e7 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNotEqualsToAssertThat.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNotEqualsToAssertThat.java @@ -23,7 +23,7 @@ import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; @@ -33,6 +33,10 @@ public class JUnitAssertNotEqualsToAssertThat extends Recipe { + private static final String JUNIT = "org.junit.jupiter.api.Assertions"; + private static final String ASSERTJ = "org.assertj.core.api.Assertions"; + private static final MethodMatcher ASSERT_NOT_EQUALS_MATCHER = new MethodMatcher(JUNIT + " assertNotEquals(..)", true); + @Override public String getDisplayName() { return "JUnit `assertNotEquals` to AssertJ"; @@ -45,117 +49,64 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", false), new AssertNotEqualsToAssertThatVisitor()); - } - - public static class AssertNotEqualsToAssertThatVisitor extends JavaIsoVisitor { - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - private static final MethodMatcher JUNIT_ASSERT_EQUALS = new MethodMatcher("org.junit.jupiter.api.Assertions" + " assertNotEquals(..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (!JUNIT_ASSERT_EQUALS.matches(method)) { - return method; - } - - List args = method.getArguments(); - - Expression expected = args.get(0); - Expression actual = args.get(1); - - if (args.size() == 2) { - method = JavaTemplate.builder("assertThat(#{any()}).isNotEqualTo(#{any()});") - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - expected - ); - } else if (args.size() == 3 && !isFloatingPointType(args.get(2))) { - Expression message = args.get(2); - - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isNotEqualTo(#{any()});") : - JavaTemplate.builder("assertThat(#{any()}).as(#{any(java.util.function.Supplier)}).isNotEqualTo(#{any()});"); - + return Preconditions.check(new UsesMethod<>(ASSERT_NOT_EQUALS_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (!ASSERT_NOT_EQUALS_MATCHER.matches(mi)) { + return mi; + } + + maybeAddImport(ASSERTJ, "assertThat", false); + maybeRemoveImport(JUNIT); + + List args = mi.getArguments(); + Expression expected = args.get(0); + Expression actual = args.get(1); + if (args.size() == 2) { + return JavaTemplate.builder("assertThat(#{any()}).isNotEqualTo(#{any()});") + .staticImports(ASSERTJ + ".assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual, expected); + } + if (args.size() == 3 && isFloatingPointType(args.get(2))) { + maybeAddImport(ASSERTJ, "within", false); + return JavaTemplate.builder("assertThat(#{any()}).isNotCloseTo(#{any()}, within(#{any()}));") + .staticImports(ASSERTJ + ".assertThat", ASSERTJ + ".within") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual, expected, args.get(2)); + } + if (args.size() == 3) { + Expression message = args.get(2); + return JavaTemplate.builder("assertThat(#{any()}).as(#{any()}).isNotEqualTo(#{any()});") + .staticImports(ASSERTJ + ".assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual, message, expected); + } + + maybeAddImport(ASSERTJ, "within", false); - method = template - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - message, - expected - ); - } else if (args.size() == 3) { - method = JavaTemplate.builder("assertThat(#{any()}).isNotCloseTo(#{any()}, within(#{any()}));") - .staticImports("org.assertj.core.api.Assertions.assertThat", "org.assertj.core.api.Assertions.within") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - expected, - args.get(2) - ); - maybeAddImport("org.assertj.core.api.Assertions", "within", false); - } else { Expression message = args.get(3); - - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isNotCloseTo(#{any()}, within(#{any()}));") : - JavaTemplate.builder("assertThat(#{any()}).as(#{any(java.util.function.Supplier)}).isNotCloseTo(#{any()}, within(#{any()}));"); - - method = template - .staticImports("org.assertj.core.api.Assertions.assertThat", "org.assertj.core.api.Assertions.within") - .javaParser(assertionsParser(ctx)) + return JavaTemplate.builder("assertThat(#{any()}).as(#{any()}).isNotCloseTo(#{any()}, within(#{any()}));") + .staticImports(ASSERTJ + ".assertThat", ASSERTJ + ".within") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - message, - expected, - args.get(2) - ); - - maybeAddImport("org.assertj.core.api.Assertions", "within", false); + .apply(getCursor(), method.getCoordinates().replace(), actual, message, expected, args.get(2)); } - //Make sure there is a static import for "org.assertj.core.api.Assertions.assertThat" (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); - - // Remove import for "org.junit.jupiter.api.Assertions" if no longer used. - maybeRemoveImport("org.junit.jupiter.api.Assertions"); + private boolean isFloatingPointType(Expression expression) { + JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(expression.getType()); + if (fullyQualified != null) { + String typeName = fullyQualified.getFullyQualifiedName(); + return "java.lang.Double".equals(typeName) || "java.lang.Float".equals(typeName); + } - return method; - } - - private static boolean isFloatingPointType(Expression expression) { - JavaType.FullyQualified fullyQualified = TypeUtils.asFullyQualified(expression.getType()); - if (fullyQualified != null) { - String typeName = fullyQualified.getFullyQualifiedName(); - return "java.lang.Double".equals(typeName) || "java.lang.Float".equals(typeName); + JavaType.Primitive parameterType = TypeUtils.asPrimitive(expression.getType()); + return parameterType == JavaType.Primitive.Double || parameterType == JavaType.Primitive.Float; } - - JavaType.Primitive parameterType = TypeUtils.asPrimitive(expression.getType()); - return parameterType == JavaType.Primitive.Double || parameterType == JavaType.Primitive.Float; - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNotNullToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNotNullToAssertThat.java index dea4c6ffe..f6d4696ad 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNotNullToAssertThat.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNotNullToAssertThat.java @@ -23,15 +23,16 @@ import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.TypeUtils; import java.util.List; public class JUnitAssertNotNullToAssertThat extends Recipe { + private static final MethodMatcher ASSERT_NOT_NULL_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertNotNull(..)", true); + @Override public String getDisplayName() { return "JUnit `assertNotNull` to AssertJ"; @@ -44,68 +45,35 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", false), new AssertNotNullToAssertThatVisitor()); - } - - public static class AssertNotNullToAssertThatVisitor extends JavaIsoVisitor { - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - private static final MethodMatcher JUNIT_ASSERT_NOT_NULL_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions" + " assertNotNull(..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (!JUNIT_ASSERT_NOT_NULL_MATCHER.matches(method)) { - return method; - } - - List args = method.getArguments(); - Expression actual = args.get(0); + return Preconditions.check(new UsesMethod<>(ASSERT_NOT_NULL_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (!ASSERT_NOT_NULL_MATCHER.matches(mi)) { + return mi; + } + + maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); + maybeRemoveImport("org.junit.jupiter.api.Assertions"); + + List args = mi.getArguments(); + Expression actual = args.get(0); + if (args.size() == 1) { + return JavaTemplate.builder("assertThat(#{any()}).isNotNull();") + .staticImports("org.assertj.core.api.Assertions.assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual); + + } - if (args.size() == 1) { - method = JavaTemplate.builder("assertThat(#{any()}).isNotNull();") - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual - ); - - } else { Expression message = args.get(1); - - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isNotNull();") : - JavaTemplate.builder("assertThat(#{any()}).as(#{any(java.util.function.Supplier)}).isNotNull();"); - - method = template + return JavaTemplate.builder("assertThat(#{any()}).as(#{any()}).isNotNull();") .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - message - ); + .apply(getCursor(), mi.getCoordinates().replace(), actual, message); } - - //Make sure there is a static import for "org.assertj.core.api.Assertions.assertThat" (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); - - //And if there are no longer references to the JUnit assertions class, we can remove the import. - maybeRemoveImport("org.junit.jupiter.api.Assertions"); - - return method; - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNullToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNullToAssertThat.java index c2c916f20..7dc9586e8 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNullToAssertThat.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertNullToAssertThat.java @@ -23,15 +23,16 @@ import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.TypeUtils; import java.util.List; public class JUnitAssertNullToAssertThat extends Recipe { + private static final MethodMatcher ASSERT_NULL_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertNull(..)", true); + @Override public String getDisplayName() { return "JUnit `assertNull` to AssertJ"; @@ -44,67 +45,34 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", false), new AssertNullToAssertThatVisitor()); - } - - public static class AssertNullToAssertThatVisitor extends JavaIsoVisitor { - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - private static final MethodMatcher JUNIT_ASSERT_NULL_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions" + " assertNull(..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (!JUNIT_ASSERT_NULL_MATCHER.matches(method)) { - return method; - } + return Preconditions.check(new UsesMethod<>(ASSERT_NULL_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (!ASSERT_NULL_MATCHER.matches(mi)) { + return mi; + } + + maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); + maybeRemoveImport("org.junit.jupiter.api.Assertions"); + + List args = mi.getArguments(); + Expression actual = args.get(0); + if (args.size() == 1) { + return JavaTemplate.builder("assertThat(#{any()}).isNull();") + .staticImports("org.assertj.core.api.Assertions.assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual); + } - List args = method.getArguments(); - Expression actual = args.get(0); - - if (args.size() == 1) { - method = JavaTemplate.builder("assertThat(#{any()}).isNull();") - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual - ); - } else { Expression message = args.get(1); - - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isNull();") : - JavaTemplate.builder("assertThat(#{any()}).as(#{any(java.util.function.Supplier)}).isNull();"); - - method = template + return JavaTemplate.builder("assertThat(#{any()}).as(#{any()}).isNull();") .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - message - ); + .apply(getCursor(), mi.getCoordinates().replace(), actual, message); } - - // Make sure there is a static import for "org.assertj.core.api.Assertions.assertThat" (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); - - // Remove import for "org.junit.jupiter.api.Assertions" if no longer used. - maybeRemoveImport("org.junit.jupiter.api.Assertions"); - - return method; - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertSameToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertSameToAssertThat.java index 1241d70e0..88a3a584e 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertSameToAssertThat.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertSameToAssertThat.java @@ -23,15 +23,16 @@ import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.TypeUtils; import java.util.List; public class JUnitAssertSameToAssertThat extends Recipe { + private static final MethodMatcher ASSERT_SAME_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertSame(..)", true); + @Override public String getDisplayName() { return "JUnit `assertSame` to AssertJ"; @@ -44,70 +45,35 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", false), new AssertSameToAssertThatVisitor()); - } - - public static class AssertSameToAssertThatVisitor extends JavaIsoVisitor { - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - private static final MethodMatcher JUNIT_ASSERT_SAME_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions" + " assertSame(..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (!JUNIT_ASSERT_SAME_MATCHER.matches(method)) { - return method; - } + return Preconditions.check(new UsesMethod<>(ASSERT_SAME_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (!ASSERT_SAME_MATCHER.matches(mi)) { + return mi; + } + + maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); + maybeRemoveImport("org.junit.jupiter.api.Assertions"); + + List args = mi.getArguments(); + Expression expected = args.get(0); + Expression actual = args.get(1); + if (args.size() == 2) { + return JavaTemplate.builder("assertThat(#{any()}).isSameAs(#{any()});") + .staticImports("org.assertj.core.api.Assertions.assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual, expected); + } - List args = method.getArguments(); - Expression expected = args.get(0); - Expression actual = args.get(1); - - if (args.size() == 2) { - method = JavaTemplate.builder("assertThat(#{any()}).isSameAs(#{any()});") - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - expected - ); - } else { Expression message = args.get(2); - - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isSameAs(#{any()});") : - JavaTemplate.builder("assertThat(#{any()}).as(#{any(java.util.function.Supplier)}).isSameAs(#{any()});"); - - method = template + return JavaTemplate.builder("assertThat(#{any()}).as(#{any()}).isSameAs(#{any()});") .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - message, - expected - ); + .apply(getCursor(), mi.getCoordinates().replace(), actual, message, expected); } - - // Make sure there is a static import for "org.assertj.core.api.Assertions.assertThat" (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); - - // Remove import for "org.junit.jupiter.api.Assertions" if no longer used. - maybeRemoveImport("org.junit.jupiter.api.Assertions"); - - return method; - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertThrowsToAssertExceptionType.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertThrowsToAssertExceptionType.java index 07f55f5b1..0960100d0 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertThrowsToAssertExceptionType.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertThrowsToAssertExceptionType.java @@ -26,9 +26,13 @@ import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; +import org.openrewrite.staticanalysis.LambdaBlockToExpression; public class JUnitAssertThrowsToAssertExceptionType extends Recipe { + private static final MethodMatcher ASSERT_THROWS_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertThrows(..)"); + private static final JavaType THROWING_CALLABLE_TYPE = JavaType.buildType("org.assertj.core.api.ThrowableAssert.ThrowingCallable"); + @Override public String getDisplayName() { return "JUnit AssertThrows to AssertJ exceptionType"; @@ -41,56 +45,38 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesMethod<>("org.junit.jupiter.api.Assertions assertThrows(..)"), new AssertExceptionTypeVisitor()); - } - - private static class AssertExceptionTypeVisitor extends JavaIsoVisitor { - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - private static final MethodMatcher ASSERT_THROWS_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertThrows(..)"); + return Preconditions.check(new UsesMethod<>(ASSERT_THROWS_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (ASSERT_THROWS_MATCHER.matches(mi) && + mi.getArguments().size() == 2 && + getCursor().getParentTreeCursor().getValue() instanceof J.Block) { + J executable = mi.getArguments().get(1); + if (executable instanceof J.Lambda) { + executable = ((J.Lambda) executable).withType(THROWING_CALLABLE_TYPE); + } else if (executable instanceof J.MemberReference) { + executable = ((J.MemberReference) executable).withType(THROWING_CALLABLE_TYPE); + } else { + executable = null; + } - private static final JavaType THROWING_CALLABLE_TYPE = JavaType.buildType("org.assertj.core.api.ThrowableAssert.ThrowingCallable"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); - if (ASSERT_THROWS_MATCHER.matches(mi) - && mi.getArguments().size() == 2 - && getCursor().getParentTreeCursor().getValue() instanceof J.Block) { - J executable = mi.getArguments().get(1); - if (executable instanceof J.Lambda) { - executable = ((J.Lambda) executable).withType(THROWING_CALLABLE_TYPE); - } else if (executable instanceof J.MemberReference) { - executable = ((J.MemberReference) executable).withType(THROWING_CALLABLE_TYPE); - } else { - executable = null; - } + if (executable != null) { + mi = JavaTemplate + .builder("assertThatExceptionOfType(#{any(java.lang.Class)}).isThrownBy(#{any(org.assertj.core.api.ThrowableAssert.ThrowingCallable)})") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .staticImports("org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType") + .build() + .apply(getCursor(), mi.getCoordinates().replace(), mi.getArguments().get(0), executable); + maybeAddImport("org.assertj.core.api.AssertionsForClassTypes", "assertThatExceptionOfType", false); + maybeRemoveImport("org.junit.jupiter.api.Assertions.assertThrows"); + maybeRemoveImport("org.junit.jupiter.api.Assertions"); - if (executable != null) { - mi = JavaTemplate - .builder("assertThatExceptionOfType(#{any(java.lang.Class)}).isThrownBy(#{any(org.assertj.core.api.ThrowableAssert.ThrowingCallable)})") - .javaParser(assertionsParser(ctx)) - .staticImports("org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType") - .build() - .apply( - getCursor(), - mi.getCoordinates().replace(), - mi.getArguments().get(0), executable - ); - maybeAddImport("org.assertj.core.api.AssertionsForClassTypes", "assertThatExceptionOfType", false); - maybeRemoveImport("org.junit.jupiter.api.Assertions.assertThrows"); - maybeRemoveImport("org.junit.jupiter.api.Assertions"); + doAfterVisit(new LambdaBlockToExpression().getVisitor()); + } } + return mi; } - return mi; - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertTrueToAssertThat.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertTrueToAssertThat.java index b478716dc..8541a4f2c 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertTrueToAssertThat.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitAssertTrueToAssertThat.java @@ -23,14 +23,16 @@ import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.TypeUtils; import java.util.List; public class JUnitAssertTrueToAssertThat extends Recipe { + + private static final MethodMatcher ASSERT_TRUE_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertTrue(boolean, ..)"); + @Override public String getDisplayName() { return "JUnit `assertTrue` to AssertJ"; @@ -43,67 +45,34 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", false), new AssertTrueToAssertThatVisitor()); - } - - public static class AssertTrueToAssertThatVisitor extends JavaIsoVisitor { - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - private static final MethodMatcher JUNIT_ASSERT_TRUE = new MethodMatcher("org.junit.jupiter.api.Assertions" + " assertTrue(boolean, ..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (!JUNIT_ASSERT_TRUE.matches(method)) { - return method; - } + return Preconditions.check(new UsesMethod<>(ASSERT_TRUE_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (!ASSERT_TRUE_MATCHER.matches(mi)) { + return mi; + } + + maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); + maybeRemoveImport("org.junit.jupiter.api.Assertions"); + + List args = mi.getArguments(); + Expression actual = args.get(0); + if (args.size() == 1) { + return JavaTemplate.builder("assertThat(#{any(boolean)}).isTrue();") + .staticImports("org.assertj.core.api.Assertions.assertThat") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), actual); + } - List args = method.getArguments(); - Expression actual = args.get(0); - - if (args.size() == 1) { - method = JavaTemplate.builder("assertThat(#{any(boolean)}).isTrue();") - .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual - ); - } else { Expression message = args.get(1); - - JavaTemplate.Builder template = TypeUtils.isString(message.getType()) ? - JavaTemplate.builder("assertThat(#{any(boolean)}).as(#{any(String)}).isTrue();") : - JavaTemplate.builder("assertThat(#{any(boolean)}).as(#{any(java.util.function.Supplier)}).isTrue();"); - - method = template + return JavaTemplate.builder("assertThat(#{any()}).as(#{any(String)}).isTrue();") .staticImports("org.assertj.core.api.Assertions.assertThat") - .javaParser(assertionsParser(ctx)) + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - actual, - message - ); + .apply(getCursor(), mi.getCoordinates().replace(), actual, message); } - - //Make sure there is a static import for "org.assertj.core.api.Assertions.assertThat" (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "assertThat", false); - - // Remove import for "org.junit.jupiter.api.Assertions" if no longer used. - maybeRemoveImport("org.junit.jupiter.api.Assertions"); - - return method; - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/JUnitFailToAssertJFail.java b/src/main/java/org/openrewrite/java/testing/assertj/JUnitFailToAssertJFail.java index b93ee3c9e..aef7ddfde 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/JUnitFailToAssertJFail.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/JUnitFailToAssertJFail.java @@ -20,14 +20,20 @@ import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.java.*; -import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.TypeUtils; +import java.util.Collections; import java.util.List; public class JUnitFailToAssertJFail extends Recipe { + + private static final String JUNIT = "org.junit.jupiter.api.Assertions"; + private static final String ASSERTJ = "org.assertj.core.api.Assertions"; + private static final MethodMatcher FAIL_MATCHER = new MethodMatcher(JUNIT + " fail(..)"); + @Override public String getDisplayName() { return "JUnit fail to AssertJ"; @@ -40,118 +46,70 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", false), new JUnitFailToAssertJFailVisitor()); - } - - public static class JUnitFailToAssertJFailVisitor extends JavaIsoVisitor { - private JavaParser.Builder assertionsParser; - - private JavaParser.Builder assertionsParser(ExecutionContext ctx) { - if (assertionsParser == null) { - assertionsParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "assertj-core-3.24"); - } - return assertionsParser; - } - - private static final MethodMatcher JUNIT_FAIL_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions" + " fail(..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - J.MethodInvocation m = method; - - if (!JUNIT_FAIL_MATCHER.matches(m)) { - return m; - } - - List args = m.getArguments(); + return Preconditions.check(new UsesMethod<>(FAIL_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = method; + if (!FAIL_MATCHER.matches(mi)) { + return mi; + } - if (args.size() == 1) { - // fail(), fail(String), fail(Supplier), fail(Throwable) - if (args.get(0) instanceof J.Empty) { - m = JavaTemplate.builder("org.assertj.core.api.Assertions.fail(\"\");") - .javaParser(assertionsParser(ctx)) - .build() - .apply(getCursor(), m.getCoordinates().replace()); - } else if (args.get(0) instanceof J.Literal || - TypeUtils.isAssignableTo("java.lang.String", args.get(0).getType())) { - m = JavaTemplate.builder("org.assertj.core.api.Assertions.fail(#{any()});") - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - m.getCoordinates().replace(), - args.get(0) - ); + List args = mi.getArguments(); + if (args.size() == 1) { + // fail(), fail(String), fail(Supplier), fail(Throwable) + if (args.get(0) instanceof J.Empty) { + mi = JavaTemplate.builder(ASSERTJ + ".fail(\"\");") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace()); + } else if (args.get(0) instanceof J.Literal || + TypeUtils.isAssignableTo("java.lang.String", args.get(0).getType())) { + mi = JavaTemplate.builder(ASSERTJ + ".fail(#{any()});") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), args.get(0)); + } else { + mi = JavaTemplate.builder(ASSERTJ + ".fail(\"\", #{any()});") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), args.get(0)); + } } else { - m = JavaTemplate.builder("org.assertj.core.api.Assertions.fail(\"\", #{any()});") - .javaParser(assertionsParser(ctx)) + // fail(String, Throwable) + String anyArgs = String.join(",", Collections.nCopies(args.size(), "#{any()}")); + mi = JavaTemplate.builder(ASSERTJ + ".fail(" + anyArgs + ");") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) .build() - .apply( - getCursor(), - m.getCoordinates().replace(), - args.get(0) - ); + .apply(getCursor(), mi.getCoordinates().replace(), args.toArray()); } - } else { - // fail(String, Throwable) - StringBuilder templateBuilder = new StringBuilder("org.assertj.core.api.Assertions.fail("); - for (int i = 0; i < args.size(); i++) { - templateBuilder.append("#{any()}"); - if (i < args.size() - 1) { - templateBuilder.append(", "); - } - } - templateBuilder.append(");"); - m = JavaTemplate.builder(templateBuilder.toString()) - .javaParser(assertionsParser(ctx)) - .build() - .apply( - getCursor(), - m.getCoordinates().replace(), - args.toArray() - ); + doAfterVisit(new RemoveUnusedImports().getVisitor()); + doAfterVisit(new UnqualifiedMethodInvocations()); + return mi; } - doAfterVisit(new RemoveUnusedImports().getVisitor()); - doAfterVisit(new UnqualifiedMethodInvocations()); - return m; - } + class UnqualifiedMethodInvocations extends JavaIsoVisitor { + private final MethodMatcher INTERNAL_FAIL_MATCHER = new MethodMatcher(ASSERTJ + " fail(..)"); - private static class UnqualifiedMethodInvocations extends JavaIsoVisitor { - private static final MethodMatcher ASSERTJ_FAIL_MATCHER = new MethodMatcher("org.assertj.core.api.Assertions" + " fail(..)"); + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + if (!INTERNAL_FAIL_MATCHER.matches(mi)) { + return mi; + } - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { - if (!ASSERTJ_FAIL_MATCHER.matches(method)) { - return method; - } + maybeAddImport(ASSERTJ, "fail", false); + maybeRemoveImport(JUNIT + ".fail"); - StringBuilder templateBuilder = new StringBuilder("fail("); - List arguments = method.getArguments(); - for (int i = 0; i < arguments.size(); i++) { - templateBuilder.append("#{any()}"); - if (i < arguments.size() - 1) { - templateBuilder.append(", "); - } + List arguments = mi.getArguments(); + String anyArgs = String.join(",", Collections.nCopies(arguments.size(), "#{any()}")); + return JavaTemplate.builder("fail(" + anyArgs + ");") + .staticImports(ASSERTJ + ".fail") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), arguments.toArray()); } - templateBuilder.append(");"); - - method = JavaTemplate.builder(templateBuilder.toString()) - .staticImports("org.assertj.core.api.Assertions" + ".fail") - .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) - .build() - .apply( - getCursor(), - method.getCoordinates().replace(), - arguments.toArray() - ); - //Make sure there is a static import for "org.assertj.core.api.Assertions.assertThat" (even if not referenced) - maybeAddImport("org.assertj.core.api.Assertions", "fail", false); - maybeRemoveImport("org.junit.jupiter.api.Assertions.fail"); - return super.visitMethodInvocation(method, ctx); } - } + }); } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/SimplifyAssertJAssertion.java b/src/main/java/org/openrewrite/java/testing/assertj/SimplifyAssertJAssertion.java new file mode 100644 index 000000000..de1d6be55 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/assertj/SimplifyAssertJAssertion.java @@ -0,0 +1,102 @@ +/* + * Copyright 2023 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.assertj; + +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +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.tree.J; +import org.openrewrite.java.tree.TypeUtils; + +@AllArgsConstructor +@NoArgsConstructor +public class SimplifyAssertJAssertion extends Recipe { + + private static final MethodMatcher ASSERT_THAT_MATCHER = new MethodMatcher("org.assertj.core.api.Assertions assertThat(..)"); + + @Option(displayName = "AssertJ assertion", + description = "The assertion method that should be replaced.", + example = "hasSize", + required = false) + @Nullable + String assertToReplace; + + @Option(displayName = "Assertion argument literal", + description = "The literal argument passed into the assertion to replace; use \"null\" for `null`.", + example = "0") + String literalArgument; + + @Option(displayName = "Dedicated assertion", + description = "The zero argument assertion to adopt instead.", + example = "isEmpty") + String dedicatedAssertion; + + @Option(displayName = "Required type", + description = "The type of the actual assertion argument.", + example = "java.lang.String") + String requiredType; + + @Override + public String getDisplayName() { + return "Simplify AssertJ assertions with literal arguments"; + } + + @Override + public String getDescription() { + return "Simplify AssertJ assertions by replacing them with more expressiove dedicated assertions."; + } + + @Override + public TreeVisitor getVisitor() { + final MethodMatcher assertToReplace = new MethodMatcher("org.assertj.core.api.* " + this.assertToReplace + "(..)"); + return new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + + // Match the end of the chain first, then the select to avoid matching the wrong method chain + if (!assertToReplace.matches(mi) || !ASSERT_THAT_MATCHER.matches(mi.getSelect())) { + return mi; + } + + // Compare argument with passed in literal + if (!(mi.getArguments().get(0) instanceof J.Literal) || + !literalArgument.equals(((J.Literal) mi.getArguments().get(0)).getValueSource())) { // Implies "null" is `null` + return mi; + } + + // Check argument type of assertThat + if (!TypeUtils.isAssignableTo(requiredType, ((J.MethodInvocation) mi.getSelect()).getArguments().get(0).getType())) { + return mi; + } + + // Assume zero argument replacement method + return JavaTemplate.builder("#{any()}." + dedicatedAssertion + "()") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), mi.getSelect()); + } + }; + } +} diff --git a/src/main/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertion.java b/src/main/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertion.java index 881bce47a..e6d37912e 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertion.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertion.java @@ -17,11 +17,11 @@ import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; +import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.Option; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; @@ -30,37 +30,35 @@ import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.TypeUtils; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; @AllArgsConstructor @NoArgsConstructor public class SimplifyChainedAssertJAssertion extends Recipe { - @Option(displayName = "AssertJ Assertion", + + @Option(displayName = "AssertJ chained assertion", description = "The chained AssertJ assertion to move to dedicated assertion.", example = "equals", required = false) @Nullable String chainedAssertion; - @Option(displayName = "AssertJ Assertion", + @Option(displayName = "AssertJ replaced assertion", description = "The AssertJ assert that should be replaced.", example = "isTrue", required = false) @Nullable String assertToReplace; - @Option(displayName = "AssertJ Assertion", + @Option(displayName = "AssertJ replacement assertion", description = "The AssertJ method to migrate to.", example = "isEqualTo", required = false) @Nullable String dedicatedAssertion; - @Option(displayName = "Required Type", - description = "Specifies the type the recipe should run on.", + @Option(displayName = "Required type", + description = "The type of the actual assertion argument.", example = "java.lang.String", required = false) @Nullable @@ -84,112 +82,89 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return new SimplifyChainedAssertJAssertionsVisitor(); - } - - private class SimplifyChainedAssertJAssertionsVisitor extends JavaIsoVisitor { - private final MethodMatcher ASSERT_THAT_MATCHER = new MethodMatcher("org.assertj.core.api.Assertions assertThat(..)"); - private final MethodMatcher CHAINED_ASSERT_MATCHER = new MethodMatcher("java..* " + chainedAssertion + "(..)"); - private final MethodMatcher ASSERT_TO_REPLACE = new MethodMatcher("org.assertj.core.api.* " + assertToReplace + "(..)"); - - @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation methodInvocation, ExecutionContext ctx) { - J.MethodInvocation mi = super.visitMethodInvocation(methodInvocation, ctx); - - // assert has correct assertion - if (!ASSERT_TO_REPLACE.matches(mi)) { - return mi; - } - - // assertThat has method call - J.MethodInvocation assertThat = (J.MethodInvocation) mi.getSelect(); - if (!ASSERT_THAT_MATCHER.matches(assertThat) || !(assertThat.getArguments().get(0) instanceof J.MethodInvocation)) { - return mi; - } - - J.MethodInvocation assertThatArg = (J.MethodInvocation) assertThat.getArguments().get(0); - if (!CHAINED_ASSERT_MATCHER.matches(assertThatArg)) { - return mi; + MethodMatcher assertThatMatcher = new MethodMatcher("org.assertj.core.api.Assertions assertThat(..)"); + MethodMatcher chainedAssertMatcher = new MethodMatcher("java..* " + chainedAssertion + "(..)"); + MethodMatcher assertToReplace = new MethodMatcher("org.assertj.core.api.* " + this.assertToReplace + "(..)"); + + return new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation methodInvocation, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(methodInvocation, ctx); + + // assert has correct assertion + if (!assertToReplace.matches(mi) || mi.getArguments().size() != 1) { + return mi; + } + + // assertThat has method call + J.MethodInvocation assertThat = (J.MethodInvocation) mi.getSelect(); + if (!assertThatMatcher.matches(assertThat) || !(assertThat.getArguments().get(0) instanceof J.MethodInvocation)) { + return mi; + } + + J.MethodInvocation assertThatArg = (J.MethodInvocation) assertThat.getArguments().get(0); + if (!chainedAssertMatcher.matches(assertThatArg)) { + return mi; + } + + // Extract the actual argument for the new assertThat call + Expression actual = assertThatArg.getSelect() != null ? assertThatArg.getSelect() : assertThatArg; + if (!TypeUtils.isAssignableTo(requiredType, actual.getType())) { + return mi; + } + List arguments = new ArrayList<>(); + arguments.add(actual); + + String template = getStringTemplateAndAppendArguments(assertThatArg, mi, arguments); + return JavaTemplate.builder(String.format(template, dedicatedAssertion)) + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "junit-jupiter-api-5.9", "assertj-core-3.24")) + .build() + .apply(getCursor(), mi.getCoordinates().replace(), arguments.toArray()); } - // Extract the actual argument for the new assertThat call - Expression actual = assertThatArg.getSelect() != null ? assertThatArg.getSelect() : assertThatArg; - if (!TypeUtils.isAssignableTo(requiredType, actual.getType())) { - return mi; + private String getStringTemplateAndAppendArguments(J.MethodInvocation assertThatArg, J.MethodInvocation methodToReplace, List arguments) { + Expression assertThatArgument = assertThatArg.getArguments().get(0); + Expression methodToReplaceArgument = methodToReplace.getArguments().get(0); + boolean assertThatArgumentIsEmpty = assertThatArgument instanceof J.Empty; + boolean methodToReplaceArgumentIsEmpty = methodToReplaceArgument instanceof J.Empty; + + // If both arguments are empty, then the select is already added to the arguments list, and we use a minimal template + if (assertThatArgumentIsEmpty && methodToReplaceArgumentIsEmpty) { + return "assertThat(#{any()}).%s()"; + } + + // If both arguments are not empty, then we add both to the arguments to the arguments list, and return a template with two arguments + if (!assertThatArgumentIsEmpty && !methodToReplaceArgumentIsEmpty) { + // This should only happen for map assertions using a key and value + arguments.add(assertThatArgument); + arguments.add(methodToReplaceArgument); + return "assertThat(#{any()}).%s(#{any()}, #{any()})"; + } + + // If either argument is empty, we choose which one to add to the arguments list, and optionally extract the select + arguments.add(extractEitherArgument(assertThatArgumentIsEmpty, assertThatArgument, methodToReplaceArgument)); + + // Special case for Path.of() assertions + if ("java.nio.file.Path".equals(requiredType) && dedicatedAssertion.contains("Raw") && + TypeUtils.isAssignableTo("java.lang.String", assertThatArgument.getType())) { + maybeAddImport("java.nio.file.Path"); + return "assertThat(#{any()}).%s(Path.of(#{any()}))"; + } + + return "assertThat(#{any()}).%s(#{any()})"; } - List arguments = new ArrayList<>(); - arguments.add(actual); - // Special case for more expressive assertions: assertThat(x.size()).isEqualTo(0) -> isEmpty() - if ("size".equals(chainedAssertion) && "isEqualTo".equals(assertToReplace) && hasZeroArgument(mi)) { - return applyTemplate("assertThat(#{any()}).isEmpty()", arguments, mi, ctx); + private Expression extractEitherArgument(boolean assertThatArgumentIsEmpty, Expression assertThatArgument, Expression methodToReplaceArgument) { + if (assertThatArgumentIsEmpty) { + return methodToReplaceArgument; + } + // Only on the assertThat argument do we possibly replace the argument with the select; such as list.size() -> list + if (chainedAssertMatcher.matches(assertThatArgument)) { + return Objects.requireNonNull(((J.MethodInvocation) assertThatArgument).getSelect()); + } + return assertThatArgument; } - - String template = getStringTemplateAndAppendArguments(assertThatArg, mi, arguments); - return applyTemplate(String.format(template, dedicatedAssertion), arguments, mi, ctx); - } - - private J.MethodInvocation applyTemplate(String formattedTemplate, List arguments, J.MethodInvocation mi, ExecutionContext ctx) { - return JavaTemplate.builder(formattedTemplate) - .contextSensitive() - .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "junit-jupiter-api-5.9", "assertj-core-3.24")) - .build() - .apply(getCursor(), mi.getCoordinates().replace(), arguments.toArray()); - } - } - - private String getStringTemplateAndAppendArguments(J.MethodInvocation assertThatArg, J.MethodInvocation methodToReplace, List arguments) { - Expression assertThatArgument = assertThatArg.getArguments().get(0); - Expression methodToReplaceArgument = methodToReplace.getArguments().get(0); - boolean assertThatArgumentIsEmpty = assertThatArgument instanceof J.Empty; - boolean methodToReplaceArgumentIsEmpty = methodToReplaceArgument instanceof J.Empty; - - // If both arguments are empty, then the select is already added to the arguments list, and we use a minimal template - if (assertThatArgumentIsEmpty && methodToReplaceArgumentIsEmpty) { - return "assertThat(#{any()}).%s()"; - } - - // If both arguments are not empty, then we add both to the arguments to the arguments list, and return a template with two arguments - if (!assertThatArgumentIsEmpty && !methodToReplaceArgumentIsEmpty) { - // This should only happen for map assertions using a key and value - arguments.add(assertThatArgument); - arguments.add(methodToReplaceArgument); - return "assertThat(#{any()}).%s(#{any()}, #{any()})"; - } - - // If either argument is empty, we choose which one to add to the arguments list, and optionally extract the select - arguments.add(extractEitherArgument(assertThatArgumentIsEmpty, assertThatArgument, methodToReplaceArgument)); - - // Special case for Path.of() assertions - if ("java.nio.file.Path".equals(requiredType) && dedicatedAssertion.contains("Raw") - && TypeUtils.isAssignableTo("java.lang.String", assertThatArgument.getType())) { - return "assertThat(#{any()}).%s(Path.of(#{any()}))"; - } - - return "assertThat(#{any()}).%s(#{any()})"; - - } - - private static Expression extractEitherArgument(boolean assertThatArgumentIsEmpty, Expression assertThatArgument, Expression methodToReplaceArgument) { - if (assertThatArgumentIsEmpty) { - return methodToReplaceArgument; - } - // Only on the assertThat argument do we possibly replace the argument with the select; such as list.size() -> list - if (assertThatArgument instanceof J.MethodInvocation) { - Expression select = ((J.MethodInvocation) assertThatArgument).getSelect(); - if (select != null) { - return select; - } - } - return assertThatArgument; - } - - private boolean hasZeroArgument(J.MethodInvocation method) { - List arguments = method.getArguments(); - if (arguments.size() == 1 && arguments.get(0) instanceof J.Literal) { - J.Literal literalArg = (J.Literal) arguments.get(0); - return literalArg.getValue() != null && literalArg.getValue().equals(0); - } - return false; + }; } } diff --git a/src/main/java/org/openrewrite/java/testing/assertj/package-info.java b/src/main/java/org/openrewrite/java/testing/assertj/package-info.java index 6f32eb963..44f3754e0 100644 --- a/src/main/java/org/openrewrite/java/testing/assertj/package-info.java +++ b/src/main/java/org/openrewrite/java/testing/assertj/package-info.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@NonNullApi +@NullMarked package org.openrewrite.java.testing.assertj; -import org.openrewrite.internal.lang.NonNullApi; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/openrewrite/java/testing/cleanup/AssertEqualsBooleanToAssertBoolean.java b/src/main/java/org/openrewrite/java/testing/cleanup/AssertEqualsBooleanToAssertBoolean.java index dd0f354cc..ca911cd05 100644 --- a/src/main/java/org/openrewrite/java/testing/cleanup/AssertEqualsBooleanToAssertBoolean.java +++ b/src/main/java/org/openrewrite/java/testing/cleanup/AssertEqualsBooleanToAssertBoolean.java @@ -57,10 +57,11 @@ public TreeVisitor getVisitor() { @Override public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation mi = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); - if (ASSERT_EQUALS.matches(mi) && isBooleanLiteral(mi)) { + if (ASSERT_EQUALS.matches(mi) && isBooleanLiteral(mi) && + JavaType.Primitive.Boolean.equals(mi.getArguments().get(1).getType())) { StringBuilder sb = new StringBuilder(); - String assertMethod = Boolean.parseBoolean(((J.Literal) mi.getArguments().get(0)).getValueSource()) - ? "assertTrue" : "assertFalse"; + String assertMethod = Boolean.parseBoolean(((J.Literal) mi.getArguments().get(0)).getValueSource()) ? + "assertTrue" : "assertFalse"; Expression assertion = mi.getArguments().get(1); if (mi.getSelect() == null) { maybeRemoveImport("org.junit.jupiter.api.Assertions"); diff --git a/src/main/java/org/openrewrite/java/testing/cleanup/AssertFalseEqualsToAssertNotEquals.java b/src/main/java/org/openrewrite/java/testing/cleanup/AssertFalseEqualsToAssertNotEquals.java index 005e94ed1..bea963e03 100644 --- a/src/main/java/org/openrewrite/java/testing/cleanup/AssertFalseEqualsToAssertNotEquals.java +++ b/src/main/java/org/openrewrite/java/testing/cleanup/AssertFalseEqualsToAssertNotEquals.java @@ -100,8 +100,8 @@ private boolean isEquals(Expression expr) { J.MethodInvocation methodInvocation = (J.MethodInvocation) expr; - return "equals".equals(methodInvocation.getName().getSimpleName()) - && methodInvocation.getArguments().size() == 1; + return "equals".equals(methodInvocation.getName().getSimpleName()) && + methodInvocation.getArguments().size() == 1; } }); } diff --git a/src/main/java/org/openrewrite/java/testing/cleanup/AssertLiteralBooleanToFail.java b/src/main/java/org/openrewrite/java/testing/cleanup/AssertLiteralBooleanToFail.java new file mode 100644 index 000000000..e1e53aea7 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/cleanup/AssertLiteralBooleanToFail.java @@ -0,0 +1,49 @@ +/* + * 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.cleanup; + +import com.google.errorprone.refaster.annotation.AfterTemplate; +import com.google.errorprone.refaster.annotation.BeforeTemplate; +import com.google.errorprone.refaster.annotation.UseImportPolicy; +import org.openrewrite.java.template.RecipeDescriptor; + +import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS; +import static org.junit.jupiter.api.Assertions.*; + +@RecipeDescriptor( + name = "Replace JUnit `assertTrue(false, \"reason\")` and `assertFalse(true, \"reason\")` with `fail(\"reason\")`", + description = "Using fail is more direct and clear." +) +public class AssertLiteralBooleanToFail { + + @BeforeTemplate + void assertFalseBefore(String message) { + assertFalse(true, message); + } + + @BeforeTemplate + void assertTrueBefore(String message) { + assertTrue(false, message); + } + + @AfterTemplate + // This annotation does not get taken into account + // resulting in Assertions.fail(message) being outputted + @UseImportPolicy(value = STATIC_IMPORT_ALWAYS) + void after(String message) { + fail(message); + } +} diff --git a/src/main/java/org/openrewrite/java/testing/cleanup/AssertNotEqualsBooleanToAssertBoolean.java b/src/main/java/org/openrewrite/java/testing/cleanup/AssertNotEqualsBooleanToAssertBoolean.java index 33dc0d81a..65d156ede 100644 --- a/src/main/java/org/openrewrite/java/testing/cleanup/AssertNotEqualsBooleanToAssertBoolean.java +++ b/src/main/java/org/openrewrite/java/testing/cleanup/AssertNotEqualsBooleanToAssertBoolean.java @@ -59,8 +59,8 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu J.MethodInvocation mi = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); if (ASSERT_NOT_EQUALS.matches(mi) && isBooleanLiteral(mi)) { StringBuilder sb = new StringBuilder(); - String assertMethod = Boolean.parseBoolean(((J.Literal) mi.getArguments().get(0)).getValueSource()) - ? "assertFalse" : "assertTrue"; + String assertMethod = Boolean.parseBoolean(((J.Literal) mi.getArguments().get(0)).getValueSource()) ? + "assertFalse" : "assertTrue"; Expression assertion = mi.getArguments().get(1); if (mi.getSelect() == null) { maybeRemoveImport("org.junit.jupiter.api.Assertions"); diff --git a/src/main/java/org/openrewrite/java/testing/cleanup/AssertTrueComparisonToAssertEquals.java b/src/main/java/org/openrewrite/java/testing/cleanup/AssertTrueComparisonToAssertEquals.java index c975d7d0a..fbcdaa352 100644 --- a/src/main/java/org/openrewrite/java/testing/cleanup/AssertTrueComparisonToAssertEquals.java +++ b/src/main/java/org/openrewrite/java/testing/cleanup/AssertTrueComparisonToAssertEquals.java @@ -115,10 +115,10 @@ private boolean isEqualBinary(J.MethodInvocation method) { // Prevent breaking identity comparison. // Objects that are compared with == should not be compared with `.equals()` instead. // Out of the primitives == is not allowed when both are of type String - return binary.getLeft().getType() instanceof JavaType.Primitive - && binary.getRight().getType() instanceof JavaType.Primitive - && !(binary.getLeft().getType() == JavaType.Primitive.String - && binary.getRight().getType() == JavaType.Primitive.String); + return binary.getLeft().getType() instanceof JavaType.Primitive && + binary.getRight().getType() instanceof JavaType.Primitive && + !(binary.getLeft().getType() == JavaType.Primitive.String && + binary.getRight().getType() == JavaType.Primitive.String); } }); } diff --git a/src/main/java/org/openrewrite/java/testing/cleanup/AssertTrueEqualsToAssertEquals.java b/src/main/java/org/openrewrite/java/testing/cleanup/AssertTrueEqualsToAssertEquals.java index 35af67a58..e8a8ab646 100644 --- a/src/main/java/org/openrewrite/java/testing/cleanup/AssertTrueEqualsToAssertEquals.java +++ b/src/main/java/org/openrewrite/java/testing/cleanup/AssertTrueEqualsToAssertEquals.java @@ -102,8 +102,8 @@ private boolean isEquals(Expression expr) { J.MethodInvocation methodInvocation = (J.MethodInvocation) expr; - return "equals".equals(methodInvocation.getName().getSimpleName()) - && methodInvocation.getArguments().size() == 1; + return "equals".equals(methodInvocation.getName().getSimpleName()) && + methodInvocation.getArguments().size() == 1; } }); } diff --git a/src/main/java/org/openrewrite/java/testing/cleanup/AssertionsArgumentOrder.java b/src/main/java/org/openrewrite/java/testing/cleanup/AssertionsArgumentOrder.java index 6ff67c3bf..2a56bcca0 100644 --- a/src/main/java/org/openrewrite/java/testing/cleanup/AssertionsArgumentOrder.java +++ b/src/main/java/org/openrewrite/java/testing/cleanup/AssertionsArgumentOrder.java @@ -37,8 +37,21 @@ public class AssertionsArgumentOrder extends Recipe { new MethodMatcher("org.junit.jupiter.api.Assertions assertEquals(..)"), new MethodMatcher("org.junit.jupiter.api.Assertions assertNotEquals(..)"), new MethodMatcher("org.junit.jupiter.api.Assertions assertSame(..)"), - new MethodMatcher("org.junit.jupiter.api.Assertions assertNotSame(..)"), - new MethodMatcher("org.junit.jupiter.api.Assertions assertArrayEquals(..)") + new MethodMatcher("org.junit.jupiter.api.Assertions assertNotSame(..)") + }; + + private static final MethodMatcher[] junitAssertMatchers = new MethodMatcher[]{ + new MethodMatcher("org.junit.Assert assertEquals(..)"), + new MethodMatcher("org.junit.Assert assertEquals(..)"), + new MethodMatcher("org.junit.Assert assertArrayEquals(..)"), + new MethodMatcher("org.junit.Assert assertSame(..)"), + new MethodMatcher("org.junit.Assert assertNotSame(..)"), + new MethodMatcher("org.junit.Assert assert*Null(String, Object)") + }; + + private static final MethodMatcher[] junitAssertWithMessageMatchers = new MethodMatcher[]{ + new MethodMatcher("org.junit.Assert assertEquals(String, ..)"), + new MethodMatcher("org.junit.Assert assertArrayEquals(String, ..)") }; private static final MethodMatcher jupiterAssertIterableEqualsMatcher = new MethodMatcher("org.junit.jupiter.api.Assertions assertIterableEquals(..)"); @@ -56,6 +69,8 @@ public class AssertionsArgumentOrder extends Recipe { static { List matchers = new ArrayList<>(Arrays.asList(jupiterAssertionMatchers)); + matchers.addAll(Arrays.asList(junitAssertMatchers)); + matchers.addAll(Arrays.asList(junitAssertWithMessageMatchers)); matchers.add(jupiterAssertIterableEqualsMatcher); matchers.add(jupiterAssertNullMatcher); matchers.addAll(Arrays.asList(testNgMatcher)); @@ -97,7 +112,10 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu final Expression expected; final Expression actual; - if (isJupiterAssertion(mi)) { + if (isJunitAssertEqualsWithMessage(mi)) { + expected = mi.getArguments().get(1); + actual = mi.getArguments().get(2); + } else if (isJunitAssertion(mi) || isJupiterAssertion(mi)) { expected = mi.getArguments().get(0); actual = mi.getArguments().get(1); } else if (isTestNgAssertion(mi)) { @@ -174,5 +192,23 @@ private boolean isTestNgAssertion(J.MethodInvocation mi) { } return false; } + + private boolean isJunitAssertion(J.MethodInvocation mi) { + for (MethodMatcher assertionMethodMatcher : junitAssertMatchers) { + if (assertionMethodMatcher.matches(mi)) { + return true; + } + } + return false; + } + + private boolean isJunitAssertEqualsWithMessage(J.MethodInvocation mi) { + for (MethodMatcher actExpMatcher : junitAssertWithMessageMatchers) { + if (actExpMatcher.matches(mi)) { + return true; + } + } + return false; + } } } diff --git a/src/main/java/org/openrewrite/java/testing/cleanup/RemoveEmptyTests.java b/src/main/java/org/openrewrite/java/testing/cleanup/RemoveEmptyTests.java index cbc914457..d2701d963 100644 --- a/src/main/java/org/openrewrite/java/testing/cleanup/RemoveEmptyTests.java +++ b/src/main/java/org/openrewrite/java/testing/cleanup/RemoveEmptyTests.java @@ -15,6 +15,7 @@ */ package org.openrewrite.java.testing.cleanup; +import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.Preconditions; import org.openrewrite.Recipe; @@ -53,8 +54,9 @@ public Set getTags() { @Override public TreeVisitor getVisitor() { return Preconditions.check(new FindEmptyMethods(false), new JavaVisitor() { + @Override - public J visitMethodDeclaration(MethodDeclaration method, ExecutionContext ctx) { + public @Nullable J visitMethodDeclaration(MethodDeclaration method, ExecutionContext ctx) { if (hasTestAnnotation(method) && isEmptyMethod(method)) { //noinspection ConstantConditions return null; diff --git a/src/main/java/org/openrewrite/java/testing/cleanup/RemoveTestPrefix.java b/src/main/java/org/openrewrite/java/testing/cleanup/RemoveTestPrefix.java index f16fe570d..a5ac744b3 100644 --- a/src/main/java/org/openrewrite/java/testing/cleanup/RemoveTestPrefix.java +++ b/src/main/java/org/openrewrite/java/testing/cleanup/RemoveTestPrefix.java @@ -31,6 +31,7 @@ import java.time.Duration; import java.util.Arrays; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; public class RemoveTestPrefix extends Recipe { @@ -83,26 +84,26 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, // Quickly reject invalid methods String simpleName = method.getSimpleName(); int nameLength = simpleName.length(); - if (nameLength < 5 - || !simpleName.startsWith("test") - || !(simpleName.charAt(4) == '_' || Character.isUpperCase(simpleName.charAt(4))) - || TypeUtils.isOverride(method.getMethodType()) - || !hasJUnit5MethodAnnotation(method)) { + if (nameLength < 5 || + !simpleName.startsWith("test") || + !(simpleName.charAt(4) == '_' || Character.isUpperCase(simpleName.charAt(4))) || + TypeUtils.isOverride(method.getMethodType()) || + !hasJUnit5MethodAnnotation(method)) { return m; } // Reject invalid start character - boolean snakecase = simpleName.charAt(4) == '_' - && 5 < nameLength - && Character.isAlphabetic(simpleName.charAt(5)); + boolean snakecase = simpleName.charAt(4) == '_' && + 5 < nameLength && + Character.isAlphabetic(simpleName.charAt(5)); if (!snakecase && !Character.isAlphabetic(simpleName.charAt(4))) { return m; } // Avoid reserved keywords - String newMethodName = snakecase - ? NameCaseConvention.format(NameCaseConvention.LOWER_UNDERSCORE, simpleName.substring(5)) - : NameCaseConvention.format(NameCaseConvention.LOWER_CAMEL, simpleName.substring(4)); + String newMethodName = snakecase ? + NameCaseConvention.format(NameCaseConvention.LOWER_UNDERSCORE, simpleName.substring(5)) : + NameCaseConvention.format(NameCaseConvention.LOWER_CAMEL, simpleName.substring(4)); if (RESERVED_KEYWORDS.contains(newMethodName)) { return m; } @@ -121,6 +122,21 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, } } + // Skip when calling a similarly named method + AtomicBoolean skip = new AtomicBoolean(false); + new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, AtomicBoolean atomicBoolean) { + if (method.getName().getSimpleName().equals(newMethodName) && method.getSelect() == null) { + skip.set(true); + } + return super.visitMethodInvocation(method, atomicBoolean); + } + }.visitMethodDeclaration(m, skip); + if (skip.get()) { + return m; + } + // Rename method and return type = type.withName(newMethodName); return m.withName(m.getName().withSimpleName(newMethodName).withType(type)) @@ -133,11 +149,11 @@ private boolean methodExists(JavaType.Method method, String newName) { private static boolean hasJUnit5MethodAnnotation(MethodDeclaration method) { for (J.Annotation a : method.getLeadingAnnotations()) { - if (TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.Test") - || TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.TestTemplate") - || TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.RepeatedTest") - || TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.params.ParameterizedTest") - || TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.TestFactory")) { + if (TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.Test") || + TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.TestTemplate") || + TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.RepeatedTest") || + TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.params.ParameterizedTest") || + TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.TestFactory")) { return true; } } diff --git a/src/main/java/org/openrewrite/java/testing/cleanup/SimplifyTestThrows.java b/src/main/java/org/openrewrite/java/testing/cleanup/SimplifyTestThrows.java index 2c97e26bf..57cd7cfab 100644 --- a/src/main/java/org/openrewrite/java/testing/cleanup/SimplifyTestThrows.java +++ b/src/main/java/org/openrewrite/java/testing/cleanup/SimplifyTestThrows.java @@ -15,8 +15,8 @@ */ package org.openrewrite.java.testing.cleanup; +import org.jspecify.annotations.Nullable; import org.openrewrite.*; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.*; @@ -63,9 +63,9 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex J.MethodDeclaration m = super.visitMethodDeclaration(method, ctx); // reject invalid methods - if (TypeUtils.isOverride(m.getMethodType()) - || !hasJUnit5MethodAnnotation(method) - || throwsNothingOrException(method)) { + if (TypeUtils.isOverride(m.getMethodType()) || + !hasJUnit5MethodAnnotation(method) || + throwsNothingOrException(method)) { return m; } @@ -101,11 +101,11 @@ private boolean throwsNothingOrException(J.MethodDeclaration method) { private boolean hasJUnit5MethodAnnotation(J.MethodDeclaration method) { for (J.Annotation a : method.getLeadingAnnotations()) { - if (TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.Test") - || TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.TestTemplate") - || TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.RepeatedTest") - || TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.params.ParameterizedTest") - || TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.TestFactory")) { + if (TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.Test") || + TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.TestTemplate") || + TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.RepeatedTest") || + TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.params.ParameterizedTest") || + TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.TestFactory")) { return true; } } diff --git a/src/main/java/org/openrewrite/java/testing/cleanup/TestsShouldIncludeAssertions.java b/src/main/java/org/openrewrite/java/testing/cleanup/TestsShouldIncludeAssertions.java index 4fe0b0f09..50ed1e10e 100644 --- a/src/main/java/org/openrewrite/java/testing/cleanup/TestsShouldIncludeAssertions.java +++ b/src/main/java/org/openrewrite/java/testing/cleanup/TestsShouldIncludeAssertions.java @@ -17,8 +17,8 @@ import lombok.EqualsAndHashCode; import lombok.Value; +import org.jspecify.annotations.Nullable; import org.openrewrite.*; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; @@ -38,20 +38,22 @@ public class TestsShouldIncludeAssertions extends Recipe { private static final List TEST_ANNOTATIONS = Collections.singletonList("org.junit.jupiter.api.Test"); private static final List DEFAULT_ASSERTIONS = Arrays.asList( + "com.github.tomakehurst.wiremock.client.WireMock", + "io.restassured", + "mockit", "org.assertj.core.api", - "org.junit.jupiter.api.Assertions", + "org.easymock", "org.hamcrest.MatcherAssert", + "org.jmock", + "org.junit.Assert", // rarely, the test annotation is junit 5 but the assert is junit 4 + "org.junit.jupiter.api.Assertions", "org.mockito.Mockito.verify", "org.mockito.Mockito.verifyNoInteractions", "org.mockito.Mockito.verifyNoMoreInteractions", "org.mockito.Mockito.verifyZeroInteractions", - "org.easymock", - "org.jmock", - "mockit", - "io.restassured", + "org.springframework.test.web.client.MockRestServiceServer.verify", "org.springframework.test.web.servlet.ResultActions", - "com.github.tomakehurst.wiremock.client.WireMock", - "org.junit.Assert"// rarely, the test annotation is junit 5 but the assert is junit 4 + "reactor.test.StepVerifier" ); @Option(displayName = "Additional assertions", @@ -68,7 +70,7 @@ public String getDisplayName() { @Override public String getDescription() { - return "For tests not having any assertions, wrap the statements with JUnit Jupiter's `Assertions#assertThrowDoesNotThrow(..)`."; + return "For tests not having any assertions, wrap the statements with JUnit Jupiter's `Assertions#assertDoesNotThrow(..)`."; } @Override @@ -97,16 +99,6 @@ public TreeVisitor getVisitor() { private static class TestShouldIncludeAssertionsVisitor extends JavaIsoVisitor { - JavaParser.Builder javaParser; - - private JavaParser.Builder javaParser(ExecutionContext ctx) { - if (javaParser == null) { - javaParser = JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "junit-jupiter-api-5.9"); - } - return javaParser; - } - private final Map> matcherPatternToClassInvocation = new HashMap<>(); private final List additionalAsserts; @@ -121,7 +113,8 @@ private static class TestShouldIncludeAssertionsVisitor extends JavaIsoVisitor #{any()});") .staticImports("org.junit.jupiter.api.Assertions.assertDoesNotThrow") - .javaParser(javaParser(ctx)) + .javaParser(JavaParser.fromJavaVersion() + .classpathFromResources(ctx, "junit-jupiter-api-5.9")) .build() .apply(updateCursor(md), md.getCoordinates().replaceBody(), body); } @@ -151,6 +145,15 @@ private boolean methodIsTest(J.MethodDeclaration methodDeclaration) { return false; } + private boolean methodIsDisabled(J.MethodDeclaration methodDeclaration) { + for (J.Annotation leadingAnnotation : methodDeclaration.getLeadingAnnotations()) { + if (TypeUtils.isOfClassType(leadingAnnotation.getType(), "org.junit.jupiter.api.Disabled")) { + return true; + } + } + return false; + } + private boolean methodHasAssertion(J.Block body) { AtomicBoolean hasAssertion = new AtomicBoolean(Boolean.FALSE); JavaIsoVisitor findAssertionVisitor = new JavaIsoVisitor() { diff --git a/src/main/java/org/openrewrite/java/testing/cleanup/TestsShouldNotBePublic.java b/src/main/java/org/openrewrite/java/testing/cleanup/TestsShouldNotBePublic.java index 7ea4850ce..dff7b613b 100644 --- a/src/main/java/org/openrewrite/java/testing/cleanup/TestsShouldNotBePublic.java +++ b/src/main/java/org/openrewrite/java/testing/cleanup/TestsShouldNotBePublic.java @@ -18,13 +18,12 @@ import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; -import lombok.Value; +import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.Option; import org.openrewrite.ScanningRecipe; import org.openrewrite.TreeVisitor; import org.openrewrite.internal.ListUtils; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.ChangeMethodAccessLevelVisitor; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.MethodMatcher; @@ -98,10 +97,10 @@ private static final class TestsNotPublicVisitor extends JavaIsoVisitor mod.getType() == J.Modifier.Type.Public) - && c.getModifiers().stream().noneMatch(mod -> mod.getType() == J.Modifier.Type.Abstract) - && !acc.extendedClasses.contains(String.valueOf(c.getType()))) { + if (c.getKind() != J.ClassDeclaration.Kind.Type.Interface && + c.getModifiers().stream().anyMatch(mod -> mod.getType() == J.Modifier.Type.Public) && + c.getModifiers().stream().noneMatch(mod -> mod.getType() == J.Modifier.Type.Abstract) && + !acc.extendedClasses.contains(String.valueOf(c.getType()))) { boolean hasTestMethods = c.getBody().getStatements().stream() .filter(org.openrewrite.java.tree.J.MethodDeclaration.class::isInstance) .map(J.MethodDeclaration.class::cast) @@ -154,9 +153,12 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex return m; } - if (m.getModifiers().stream().anyMatch(mod -> (mod.getType() == J.Modifier.Type.Public || (orProtected && mod.getType() == J.Modifier.Type.Protected))) - && Boolean.FALSE.equals(TypeUtils.isOverride(method.getMethodType())) - && hasJUnit5MethodAnnotation(m)) { + if (m.hasModifier(J.Modifier.Type.Abstract) || TypeUtils.isOverride(method.getMethodType())) { + return m; + } + + if ((m.hasModifier(J.Modifier.Type.Public) || (orProtected && m.hasModifier(J.Modifier.Type.Protected))) && + hasJUnit5MethodAnnotation(m)) { // remove public modifier doAfterVisit(new ChangeMethodAccessLevelVisitor<>(new MethodMatcher(method), null)); } @@ -166,14 +168,14 @@ && hasJUnit5MethodAnnotation(m)) { private boolean hasJUnit5MethodAnnotation(J.MethodDeclaration method) { for (J.Annotation a : method.getLeadingAnnotations()) { - if (TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.Test") - || TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.RepeatedTest") - || TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.params.ParameterizedTest") - || TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.TestFactory") - || TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.AfterEach") - || TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.BeforeEach") - || TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.AfterAll") - || TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.BeforeAll")) { + if (TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.Test") || + TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.RepeatedTest") || + TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.params.ParameterizedTest") || + TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.TestFactory") || + TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.AfterEach") || + TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.BeforeEach") || + TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.AfterAll") || + TypeUtils.isOfClassType(a.getType(), "org.junit.jupiter.api.BeforeAll")) { return true; } } diff --git a/src/main/java/org/openrewrite/java/testing/cleanup/package-info.java b/src/main/java/org/openrewrite/java/testing/cleanup/package-info.java index 100496815..59fc9b2c0 100644 --- a/src/main/java/org/openrewrite/java/testing/cleanup/package-info.java +++ b/src/main/java/org/openrewrite/java/testing/cleanup/package-info.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@NonNullApi +@NullMarked package org.openrewrite.java.testing.cleanup; -import org.openrewrite.internal.lang.NonNullApi; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotation.java b/src/main/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotation.java new file mode 100644 index 000000000..93c1ac817 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotation.java @@ -0,0 +1,265 @@ +/* + * Copyright 2024 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.dbrider; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.Space; + +import java.util.Comparator; +import java.util.List; + +public class ExecutionListenerToDbRiderAnnotation extends Recipe { + + private static final AnnotationMatcher EXECUTION_LISTENER_ANNOTATION_MATCHER = new AnnotationMatcher("@org.springframework.test.context.TestExecutionListeners"); + private static final AnnotationMatcher DBRIDER_ANNOTATION_MATCHER = new AnnotationMatcher("@com.github.database.rider.junit5.api.DBRider"); + private static final String DBRIDER_TEST_EXECUTION_LISTENER = "com.github.database.rider.spring.DBRiderTestExecutionListener"; + + @Override + public String getDisplayName() { + return "Migrate the `DBRiderTestExecutionListener` to the `@DBRider` annotation"; + } + + @Override + public String getDescription() { + return "Migrate the `DBRiderTestExecutionListener` to the `@DBRider` annotation. " + + "This recipe is useful when migrating from JUnit 4 `dbrider-spring` to JUnit 5 `dbrider-junit5`."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesType<>(DBRIDER_TEST_EXECUTION_LISTENER, true), new JavaIsoVisitor() { + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDeclaration, ExecutionContext ctx) { + J.ClassDeclaration cd = super.visitClassDeclaration(classDeclaration, ctx); + DbRiderExecutionListenerContext context = DbRiderExecutionListenerContext.ofClass(cd); + if (!context.shouldMigrate()) { + return cd; + } + if (context.shouldAddDbRiderAnnotation()) { + cd = JavaTemplate.builder("@DBRider") + .imports("com.github.database.rider.junit5.api.DBRider") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "rider-junit5-1.44")) + .build() + .apply(getCursor(), cd.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName))); + maybeAddImport("com.github.database.rider.junit5.api.DBRider"); + } + Space prefix = cd.getLeadingAnnotations().get(cd.getLeadingAnnotations().size() - 1).getPrefix(); + return cd.withLeadingAnnotations(ListUtils.map(cd.getLeadingAnnotations(), annotation -> { + if (annotation != null && EXECUTION_LISTENER_ANNOTATION_MATCHER.matches(annotation)) { + J.Annotation executionListenerAnnotation = context.getExecutionListenerAnnotation(); + maybeRemoveImport(DBRIDER_TEST_EXECUTION_LISTENER); + maybeRemoveImport("org.springframework.test.context.TestExecutionListeners.MergeMode"); + maybeRemoveImport("org.springframework.test.context.TestExecutionListeners"); + if (executionListenerAnnotation != null) { + return executionListenerAnnotation + .withArguments(firstItemPrefixWorkaround(executionListenerAnnotation.getArguments())) + .withPrefix(prefix); + } + return null; + } + return annotation; + })); + } + }); + } + + private static class DbRiderExecutionListenerContext { + private J.@Nullable Annotation testExecutionListenerAnnotation; + private boolean dbriderFound = false; + private J.@Nullable NewArray listeners; + private J.@Nullable FieldAccess listener; + private @Nullable Expression inheritListeners; + private @Nullable Expression mergeMode; + + static DbRiderExecutionListenerContext ofClass(J.ClassDeclaration clazz) { + DbRiderExecutionListenerContext context = new DbRiderExecutionListenerContext(); + clazz.getLeadingAnnotations().forEach(annotation -> { + if (EXECUTION_LISTENER_ANNOTATION_MATCHER.matches(annotation)) { + context.testExecutionListenersFound(annotation); + } else if (DBRIDER_ANNOTATION_MATCHER.matches(annotation)) { + context.dbriderFound = true; + } + }); + return context; + } + + private void testExecutionListenersFound(final J.Annotation annotation) { + testExecutionListenerAnnotation = annotation; + if (annotation.getArguments() != null) { + annotation.getArguments().forEach(arg -> { + if (arg instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) arg; + switch (((J.Identifier) assignment.getVariable()).getSimpleName()) { + case "value": + case "listeners": + if (assignment.getAssignment() instanceof J.NewArray) { + listeners = (J.NewArray) assignment.getAssignment(); + } + break; + case "inheritListeners": + inheritListeners = assignment.getAssignment(); + break; + case "mergeMode": + mergeMode = assignment.getAssignment(); + break; + } + } else if (arg instanceof J.NewArray) { + listeners = (J.NewArray) arg; + } else if (arg instanceof J.FieldAccess) { + listener = (J.FieldAccess) arg; + } + }); + } + } + + public boolean shouldMigrate() { + return isTestExecutionListenerForDbRider() && !dbriderFound; + } + + public boolean shouldAddDbRiderAnnotation() { + if (dbriderFound) { + return false; + } + + return isTestExecutionListenerForDbRider(); + } + + public J.@Nullable Annotation getExecutionListenerAnnotation() { + if (isTestExecutionListenerForDbRider()) { + if (canTestExecutionListenerBeRemoved()) { + return null; + } + if (testExecutionListenerAnnotation != null && testExecutionListenerAnnotation.getArguments() != null) { + return testExecutionListenerAnnotation.withArguments(ListUtils.map(testExecutionListenerAnnotation.getArguments(), arg -> { + if (arg instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) arg; + Expression newValue = assignment.getAssignment(); + switch (((J.Identifier) assignment.getVariable()).getSimpleName()) { + case "value": + case "listeners": + if (assignment.getAssignment() instanceof J.NewArray) { + newValue = getMigratedListeners(); + } + break; + case "inheritListeners": + newValue = getMigratedInheritListeners(); + break; + case "mergeMode": + newValue = getMigratedMergeMode(); + break; + } + if (newValue == null) { + return null; + } + return assignment.withAssignment(newValue); + } else if (arg instanceof J.NewArray) { + return getMigratedListeners(); + } + if (arg instanceof J.FieldAccess && isTypeReference(arg, DBRIDER_TEST_EXECUTION_LISTENER)) { + return null; + } + return arg; + })); + } + } + + return testExecutionListenerAnnotation; + } + + // We can only remove an execution listener annotation if: + // - InheritListeners was null or true + // - MergeMode was TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS + // By default, the TestExecutionListeners.MergeMode is REPLACE_DEFAULTS so if we remove the annotation, other defaults would kick in. + private boolean canTestExecutionListenerBeRemoved() { + if (listener == null && listeners != null && listeners.getInitializer() != null && + listeners.getInitializer().stream().allMatch(listener -> isTypeReference(listener, DBRIDER_TEST_EXECUTION_LISTENER))) { + return (getMigratedInheritListeners() == null && getMigratedMergeMode() != null); + } + return false; + } + + private @Nullable Expression getMigratedMergeMode() { + if (mergeMode != null && mergeMode instanceof J.FieldAccess && "REPLACE_DEFAULTS".equals(((J.FieldAccess) mergeMode).getName().getSimpleName())) { + return null; + } + return mergeMode; + } + + private @Nullable Expression getMigratedInheritListeners() { + if (inheritListeners != null && (inheritListeners instanceof J.Literal && Boolean.TRUE.equals(((J.Literal) inheritListeners).getValue()))) { + return null; + } + return inheritListeners; + } + + // Remove the DBRiderTestExecutionListener from the listeners array + // If the listeners array is empty after removing the DBRiderTestExecutionListener, return null so that the array itself can be removed + private J.@Nullable NewArray getMigratedListeners() { + if (listeners != null && listeners.getInitializer() != null) { + List newListeners = ListUtils.map(listeners.getInitializer(), listener -> { + if (listener instanceof J.FieldAccess && isTypeReference(listener, DBRIDER_TEST_EXECUTION_LISTENER)) { + return null; + } + return listener; + }); + if (newListeners.isEmpty()) { + return null; + } + return listeners.withInitializer(firstItemPrefixWorkaround(newListeners)); + } + return listeners; + } + + private boolean isTestExecutionListenerForDbRider() { + if (listener != null) { + return isTypeReference(listener, DBRIDER_TEST_EXECUTION_LISTENER); + } + if (listeners != null && listeners.getInitializer() != null) { + return listeners.getInitializer().stream().anyMatch(listener -> isTypeReference(listener, DBRIDER_TEST_EXECUTION_LISTENER)); + } + return false; + } + + private static boolean isTypeReference(Expression expression, String type) { + return expression.getType() instanceof JavaType.Parameterized && + ((JavaType.Parameterized) expression.getType()).getFullyQualifiedName().equals("java.lang.Class") && + ((JavaType.Parameterized) expression.getType()).getTypeParameters().size() == 1 && + ((JavaType.Parameterized) expression.getType()).getTypeParameters().get(0) instanceof JavaType.Class && + ((JavaType.Class) ((JavaType.Parameterized) expression.getType()).getTypeParameters().get(0)).getFullyQualifiedName().equals(type); + } + } + + private static @Nullable List firstItemPrefixWorkaround(@Nullable List list) { + if (list == null || list.isEmpty()) { + return list; + } + return ListUtils.mapFirst(list, t -> t.withPrefix(t.getPrefix().withWhitespace(t.getPrefix().getLastWhitespace().replaceAll(" $", "")))); + } +} diff --git a/src/main/java/org/openrewrite/java/testing/dbrider/package-info.java b/src/main/java/org/openrewrite/java/testing/dbrider/package-info.java new file mode 100644 index 000000000..8a8f30054 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/dbrider/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2024 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. + */ +@NullMarked +@NonNullFields +package org.openrewrite.java.testing.dbrider; + +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/main/java/org/openrewrite/java/testing/hamcrest/HamcrestMatcherToAssertJ.java b/src/main/java/org/openrewrite/java/testing/hamcrest/HamcrestMatcherToAssertJ.java index 2003876fb..345373b61 100644 --- a/src/main/java/org/openrewrite/java/testing/hamcrest/HamcrestMatcherToAssertJ.java +++ b/src/main/java/org/openrewrite/java/testing/hamcrest/HamcrestMatcherToAssertJ.java @@ -17,8 +17,8 @@ import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; +import org.jspecify.annotations.Nullable; import org.openrewrite.*; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; @@ -37,21 +37,21 @@ @AllArgsConstructor public class HamcrestMatcherToAssertJ extends Recipe { - @Option(displayName = "Hamcrest Matcher", + @Option(displayName = "Hamcrest matcher", description = "The Hamcrest `Matcher` to migrate to JUnit5.", example = "equalTo", required = false) @Nullable String matcher; - @Option(displayName = "AssertJ Assertion", + @Option(displayName = "AssertJ assertion", description = "The AssertJ method to migrate to.", example = "isEqualTo", required = false) @Nullable String assertion; - @Option(displayName = "Argument Type", + @Option(displayName = "Argument type", description = "The type of the argument to the Hamcrest `Matcher`.", example = "java.math.BigDecimal", required = false) diff --git a/src/main/java/org/openrewrite/java/testing/hamcrest/HamcrestNotMatcherToAssertJ.java b/src/main/java/org/openrewrite/java/testing/hamcrest/HamcrestNotMatcherToAssertJ.java index eaa9b8dbe..1f235ea35 100644 --- a/src/main/java/org/openrewrite/java/testing/hamcrest/HamcrestNotMatcherToAssertJ.java +++ b/src/main/java/org/openrewrite/java/testing/hamcrest/HamcrestNotMatcherToAssertJ.java @@ -17,8 +17,8 @@ import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; +import org.jspecify.annotations.Nullable; import org.openrewrite.*; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; diff --git a/src/main/java/org/openrewrite/java/testing/hamcrest/package-info.java b/src/main/java/org/openrewrite/java/testing/hamcrest/package-info.java index 7f9f59ac6..e6e451241 100644 --- a/src/main/java/org/openrewrite/java/testing/hamcrest/package-info.java +++ b/src/main/java/org/openrewrite/java/testing/hamcrest/package-info.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@NonNullApi +@NullMarked @NonNullFields package org.openrewrite.java.testing.hamcrest; -import org.openrewrite.internal.lang.NonNullApi; +import org.jspecify.annotations.NullMarked; import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/main/java/org/openrewrite/java/testing/jmockit/ArgumentMatchersRewriter.java b/src/main/java/org/openrewrite/java/testing/jmockit/ArgumentMatchersRewriter.java index 7d5efe007..71694a704 100644 --- a/src/main/java/org/openrewrite/java/testing/jmockit/ArgumentMatchersRewriter.java +++ b/src/main/java/org/openrewrite/java/testing/jmockit/ArgumentMatchersRewriter.java @@ -60,6 +60,7 @@ class ArgumentMatchersRewriter { } private static final Map PRIMITIVE_TO_MOCKITO_ARGUMENT_MATCHER = new HashMap<>(); + static { PRIMITIVE_TO_MOCKITO_ARGUMENT_MATCHER.put(JavaType.Primitive.Int, "anyInt"); PRIMITIVE_TO_MOCKITO_ARGUMENT_MATCHER.put(JavaType.Primitive.Long, "anyLong"); @@ -81,7 +82,7 @@ class ArgumentMatchersRewriter { this.expectationsBlock = expectationsBlock; } - J.Block rewriteExpectationsBlock() { + J.Block rewriteJMockitBlock() { List newStatements = new ArrayList<>(expectationsBlock.getStatements().size()); for (Statement expectationStatement : expectationsBlock.getStatements()) { // for each statement, check if it's a method invocation and replace any argument matchers @@ -98,17 +99,23 @@ private J.MethodInvocation rewriteMethodInvocation(J.MethodInvocation invocation if (invocation.getSelect() instanceof J.MethodInvocation) { invocation = invocation.withSelect(rewriteMethodInvocation((J.MethodInvocation) invocation.getSelect())); } + // in mockito, argument matchers must be used for all arguments or none - boolean hasArgumentMatcher = false; List arguments = invocation.getArguments(); - for (Expression methodArgument : arguments) { - if (isJmockitArgumentMatcher(methodArgument)) { - hasArgumentMatcher = true; - break; + // replace this.matcher with matcher, otherwise it's ignored + arguments.replaceAll(arg -> { + if (arg instanceof J.FieldAccess) { + J.FieldAccess fieldAccess = (J.FieldAccess) arg; + if (fieldAccess.getTarget() instanceof J.Identifier && + "this".equals(((J.Identifier) fieldAccess.getTarget()).getSimpleName())) { + return fieldAccess.getName(); + } } - } + return arg; + }); + // if there are no argument matchers, return the invocation as-is - if (!hasArgumentMatcher) { + if (arguments.stream().noneMatch(ArgumentMatchersRewriter::isJmockitArgumentMatcher)) { return invocation; } // replace each argument with the appropriate argument matcher @@ -144,9 +151,9 @@ private Expression rewriteMethodArgument(Expression methodArgument) { // ((int) any) to anyInt(), ((long) any) to anyLong(), etc argumentMatcher = PRIMITIVE_TO_MOCKITO_ARGUMENT_MATCHER.get(type); template = argumentMatcher + "()"; - } else if (type instanceof JavaType.FullyQualified) { - // (() any) to any(.class) - return rewriteFullyQualifiedToArgumentMatcher(methodArgument, (JavaType.FullyQualified) type); + } else if (type instanceof JavaType.FullyQualified || type instanceof JavaType.Array) { + // (() any) to any(.class), type can also be simple array + return rewriteAnyWithClassParameterToArgumentMatcher(methodArgument, type); } if (template == null || argumentMatcher == null) { // unhandled type, return argument unchanged @@ -156,7 +163,7 @@ private Expression rewriteMethodArgument(Expression methodArgument) { } private Expression applyArgumentTemplate(Expression methodArgument, String argumentMatcher, String template, - List templateParams) { + List templateParams) { visitor.maybeAddImport("org.mockito.Mockito", argumentMatcher); return JavaTemplate.builder(template) .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "mockito-core-3.12")) @@ -165,8 +172,7 @@ private Expression applyArgumentTemplate(Expression methodArgument, String argum .apply( new Cursor(visitor.getCursor(), methodArgument), methodArgument.getCoordinates().replace(), - templateParams.toArray() - ); + templateParams.toArray()); } private Expression applyClassArgumentTemplate(Expression methodArgument, JavaType.FullyQualified type) { @@ -178,45 +184,72 @@ private Expression applyClassArgumentTemplate(Expression methodArgument, JavaTyp .apply( new Cursor(visitor.getCursor(), methodArgument), methodArgument.getCoordinates().replace(), - type.getClassName() - )) + type.getClassName())) .withType(type); } - private Expression rewriteFullyQualifiedToArgumentMatcher(Expression methodArgument, JavaType.FullyQualified type) { + private Expression rewriteAnyWithClassParameterToArgumentMatcher(Expression methodArgument, JavaType type) { String template; List templateParams = new ArrayList<>(); - String argumentMatcher = FQN_TO_MOCKITO_ARGUMENT_MATCHER.get(type.getFullyQualifiedName()); - if (argumentMatcher != null) { - // mockito has convenience argument matchers - template = argumentMatcher + "()"; - return applyArgumentTemplate(methodArgument, argumentMatcher, template, templateParams); + + if (type instanceof JavaType.FullyQualified) { + JavaType.FullyQualified fq = (JavaType.FullyQualified) type; + String argumentMatcher = FQN_TO_MOCKITO_ARGUMENT_MATCHER.get(fq.getFullyQualifiedName()); + if (argumentMatcher != null) { + // mockito has convenience argument matchers + template = argumentMatcher + "()"; + return applyArgumentTemplate(methodArgument, argumentMatcher, template, templateParams); + } } // mockito uses any(Class) for all other types - argumentMatcher = "any"; + String argumentMatcher = "any"; template = argumentMatcher + "(#{any(java.lang.Class)})"; - templateParams.add(applyClassArgumentTemplate(methodArgument, type)); + + if (type instanceof JavaType.FullyQualified) { + templateParams.add(applyClassArgumentTemplate(methodArgument, (JavaType.FullyQualified) type)); + } else if (type instanceof JavaType.Array) { + templateParams.add(applyArrayClassArgumentTemplate(methodArgument, ((JavaType.Array) type).getElemType())); + } J.MethodInvocation invocationArgument = (J.MethodInvocation) applyArgumentTemplate(methodArgument, argumentMatcher, template, templateParams); // update the Class type parameter and method return type Expression classArgument = (Expression) templateParams.get(0); - if (classArgument.getType() == null - || invocationArgument.getMethodType() == null - || invocationArgument.getMethodType().getParameterTypes().size() != 1 - || !(invocationArgument.getMethodType().getParameterTypes().get(0) instanceof JavaType.Parameterized)) { + if (classArgument.getType() == null || + invocationArgument.getMethodType() == null || + invocationArgument.getMethodType().getParameterTypes().size() != 1 || + !(invocationArgument.getMethodType().getParameterTypes().get(0) instanceof JavaType.Parameterized)) { return invocationArgument; } - JavaType.Parameterized newParameterType = - ((JavaType.Parameterized) invocationArgument.getMethodType().getParameterTypes().get(0)) - .withTypeParameters(Collections.singletonList(classArgument.getType())); + JavaType.Parameterized newParameterType = ((JavaType.Parameterized) invocationArgument.getMethodType() + .getParameterTypes().get(0)) + .withTypeParameters(Collections.singletonList(classArgument.getType())); JavaType.Method newMethodType = invocationArgument.getMethodType() .withReturnType(classArgument.getType()) .withParameterTypes(Collections.singletonList(newParameterType)); return invocationArgument.withMethodType(newMethodType); } + private Expression applyArrayClassArgumentTemplate(Expression methodArgument, JavaType elementType) { + String newArrayElementClassName = ""; + if (elementType instanceof JavaType.FullyQualified) { + newArrayElementClassName = ((JavaType.FullyQualified) elementType).getClassName(); + } else if (elementType instanceof JavaType.Primitive) { + newArrayElementClassName = ((JavaType.Primitive) elementType).getKeyword(); + } else { + newArrayElementClassName = elementType.getClass().getName(); + } + + return JavaTemplate.builder("#{}[].class") + .javaParser(JavaParser.fromJavaVersion()) + .build() + .apply( + new Cursor(visitor.getCursor(), methodArgument), + methodArgument.getCoordinates().replace(), + newArrayElementClassName); + } + private static boolean isJmockitArgumentMatcher(Expression expression) { if (expression instanceof J.TypeCast) { expression = ((J.TypeCast) expression).getExpression(); diff --git a/src/main/java/org/openrewrite/java/testing/jmockit/ExpectationsBlockRewriter.java b/src/main/java/org/openrewrite/java/testing/jmockit/ExpectationsBlockRewriter.java deleted file mode 100644 index c45b0b798..000000000 --- a/src/main/java/org/openrewrite/java/testing/jmockit/ExpectationsBlockRewriter.java +++ /dev/null @@ -1,409 +0,0 @@ -/* - * Copyright 2024 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.jmockit; - -import org.openrewrite.Cursor; -import org.openrewrite.ExecutionContext; -import org.openrewrite.java.JavaParser; -import org.openrewrite.java.JavaTemplate; -import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.tree.*; - -import java.util.ArrayList; -import java.util.List; - -class ExpectationsBlockRewriter { - - private static final String WHEN_TEMPLATE_PREFIX = "when(#{any()})."; - 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 String getObjectTemplateField(String fqn) { - return "#{any(" + fqn + ")}"; - } - - private final JavaVisitor visitor; - private final ExecutionContext ctx; - private final J.NewClass newExpectations; - // index of the Expectations block in the method body - private final int bodyStatementIndex; - private J.Block methodBody; - private JavaCoordinates nextStatementCoordinates; - - private boolean expectationsRewriteFailed = false; - - boolean isExpectationsRewriteFailed() { - return expectationsRewriteFailed; - } - - // keep track of the additional statements being added to the method body, which impacts the statement indices - // used with bodyStatementIndex to obtain the coordinates of the next statement to be written - private int numStatementsAdded = 0; - - ExpectationsBlockRewriter(JavaVisitor visitor, ExecutionContext ctx, J.Block methodBody, - J.NewClass newExpectations, int bodyStatementIndex) { - this.visitor = visitor; - this.ctx = ctx; - this.methodBody = methodBody; - this.newExpectations = newExpectations; - this.bodyStatementIndex = bodyStatementIndex; - nextStatementCoordinates = newExpectations.getCoordinates().replace(); - } - - J.Block rewriteMethodBody() { - visitor.maybeRemoveImport("mockit.Expectations"); - - assert newExpectations.getBody() != null; - J.Block expectationsBlock = (J.Block) newExpectations.getBody().getStatements().get(0); - if (expectationsBlock.getStatements().isEmpty()) { - // empty Expectations block, remove it - removeExpectationsStatement(); - return methodBody; - } - - // rewrite the argument matchers in the expectations block - ArgumentMatchersRewriter amr = new ArgumentMatchersRewriter(visitor, ctx, expectationsBlock); - expectationsBlock = amr.rewriteExpectationsBlock(); - - // iterate over the expectations statements and rebuild the method body - List expectationStatements = new ArrayList<>(); - for (Statement expectationStatement : expectationsBlock.getStatements()) { - if (expectationStatement instanceof J.MethodInvocation) { - // handle returns statements - J.MethodInvocation invocation = (J.MethodInvocation) expectationStatement; - if (invocation.getSelect() == null && invocation.getName().getSimpleName().equals("returns")) { - expectationStatements.add(expectationStatement); - continue; - } - // if a new method invocation is found, apply the template to the previous statements - if (!expectationStatements.isEmpty()) { - // apply template to build new method body - rewriteMethodBody(expectationStatements); - - // reset statements for next expectation - expectationStatements = new ArrayList<>(); - } - } - expectationStatements.add(expectationStatement); - } - - // handle the last statement - if (!expectationStatements.isEmpty()) { - rewriteMethodBody(expectationStatements); - } - - return methodBody; - } - - private void rewriteMethodBody(List expectationStatements) { - final MockInvocationResults mockInvocationResults = buildMockInvocationResults(expectationStatements); - if (mockInvocationResults == null || !(expectationStatements.get(0) instanceof J.MethodInvocation)) { - // invalid Expectations block, cannot rewrite - expectationsRewriteFailed = true; - return; - } - J.MethodInvocation invocation = (J.MethodInvocation) expectationStatements.get(0); - if (!mockInvocationResults.getResults().isEmpty()) { - // rewrite the statement to mockito if there are results - rewriteExpectationResult(mockInvocationResults.getResults(), invocation); - } else if (nextStatementCoordinates.isReplacement()) { - // if there are no results and the Expectations block is not yet replaced, remove it - removeExpectationsStatement(); - } - if (mockInvocationResults.getTimes() != null) { - writeMethodVerification(invocation, mockInvocationResults.getTimes(), "times"); - } - if (mockInvocationResults.getMinTimes() != null) { - writeMethodVerification(invocation, mockInvocationResults.getMinTimes(), "atLeast"); - } - if (mockInvocationResults.getMaxTimes() != null) { - writeMethodVerification(invocation, mockInvocationResults.getMaxTimes(), "atMost"); - } - } - - private void rewriteExpectationResult(List results, J.MethodInvocation invocation) { - String template = getMockitoStatementTemplate(results); - if (template == null) { - // invalid template, cannot rewrite - expectationsRewriteFailed = true; - return; - } - visitor.maybeAddImport("org.mockito.Mockito", "when"); - - List templateParams = new ArrayList<>(); - templateParams.add(invocation); - templateParams.addAll(results); - - methodBody = JavaTemplate.builder(template) - .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "mockito-core-3.12")) - .staticImports("org.mockito.Mockito.*") - .build() - .apply( - new Cursor(visitor.getCursor(), methodBody), - nextStatementCoordinates, - templateParams.toArray() - ); - if (!nextStatementCoordinates.isReplacement()) { - numStatementsAdded += 1; - } - - // the next statement coordinates are directly after the most recently written statement - nextStatementCoordinates = methodBody.getStatements().get(bodyStatementIndex + numStatementsAdded) - .getCoordinates().after(); - } - - private void removeExpectationsStatement() { - methodBody = JavaTemplate.builder("") - .javaParser(JavaParser.fromJavaVersion()) - .build() - .apply( - new Cursor(visitor.getCursor(), methodBody), - nextStatementCoordinates - ); - - // the next statement coordinates are directly after the most recently added statement, or the first statement - // of the test method body if the Expectations block was the first statement - nextStatementCoordinates = bodyStatementIndex == 0 ? methodBody.getCoordinates().firstStatement() : - methodBody.getStatements().get(bodyStatementIndex + numStatementsAdded).getCoordinates().after(); - } - - private void writeMethodVerification(J.MethodInvocation invocation, Expression times, String verificationMode) { - String fqn = getInvocationSelectFullyQualifiedClassName(invocation); - if (fqn == null) { - // cannot write a verification statement for an invocation without a select field - return; - } - visitor.maybeAddImport("org.mockito.Mockito", "verify"); - visitor.maybeAddImport("org.mockito.Mockito", verificationMode); - - List templateParams = new ArrayList<>(); - templateParams.add(invocation.getSelect()); - templateParams.add(times); - templateParams.add(invocation.getName().getSimpleName()); - - String verifyTemplate = getVerifyTemplate(invocation.getArguments(), fqn, verificationMode, templateParams); - methodBody = JavaTemplate.builder(verifyTemplate) - .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "mockito-core-3.12")) - .staticImports("org.mockito.Mockito.*") - .imports(fqn) - .build() - .apply( - new Cursor(visitor.getCursor(), methodBody), - methodBody.getCoordinates().lastStatement(), - templateParams.toArray() - ); - } - - private static String getMockitoStatementTemplate(List results) { - boolean buildingResults = false; - final StringBuilder templateBuilder = new StringBuilder(WHEN_TEMPLATE_PREFIX); - for (Expression result : results) { - JavaType resultType = result.getType(); - if (result instanceof J.Literal) { - appendToTemplate(templateBuilder, buildingResults, RETURN_TEMPLATE_PREFIX, LITERAL_TEMPLATE_FIELD); - } else if (resultType instanceof JavaType.Primitive) { - String primitiveTemplateField = getPrimitiveTemplateField((JavaType.Primitive) resultType); - if (primitiveTemplateField == null) { - // unhandled primitive type - return null; - } - appendToTemplate(templateBuilder, buildingResults, RETURN_TEMPLATE_PREFIX, primitiveTemplateField); - } else if (TypeUtils.isAssignableTo(Throwable.class.getName(), resultType)) { - appendToTemplate(templateBuilder, buildingResults, THROW_TEMPLATE_PREFIX, ANY_TEMPLATE_FIELD); - } else if (resultType instanceof JavaType.Class) { - appendToTemplate(templateBuilder, buildingResults, RETURN_TEMPLATE_PREFIX, - getObjectTemplateField(((JavaType.Class) resultType).getFullyQualifiedName())); - } else if (resultType instanceof JavaType.Parameterized) { - appendToTemplate(templateBuilder, buildingResults, RETURN_TEMPLATE_PREFIX, - getObjectTemplateField(((JavaType.Parameterized) resultType).getType().getFullyQualifiedName())); - } else { - // unhandled result type - return null; - } - buildingResults = true; - } - templateBuilder.append(");"); - return templateBuilder.toString(); - } - - private static void appendToTemplate(StringBuilder templateBuilder, boolean buildingResults, String templatePrefix, - String templateField) { - if (!buildingResults) { - templateBuilder.append(templatePrefix); - } else { - templateBuilder.append(", "); - } - templateBuilder.append(templateField); - } - - private static String getVerifyTemplate(List arguments, String fqn, String verificationMode, List templateParams) { - if (arguments.isEmpty()) { - return "verify(#{any(" + fqn + ")}, " - + verificationMode - + "(#{any(int)})).#{}();"; - } - StringBuilder templateBuilder = new StringBuilder("verify(#{any(" + fqn + ")}, " - + verificationMode - + "(#{any(int)})).#{}("); - boolean hasArgument = false; - for (Expression argument : arguments) { - if (argument instanceof J.Empty) { - continue; - } else if (argument instanceof J.Literal) { - templateBuilder.append(((J.Literal) argument).getValueSource()); - } else { - templateBuilder.append("#{any()}"); - templateParams.add(argument); - } - hasArgument = true; - templateBuilder.append(", "); - } - if (hasArgument) { - templateBuilder.delete(templateBuilder.length() - 2, templateBuilder.length()); - } - templateBuilder.append(");"); - return templateBuilder.toString(); - } - - private static MockInvocationResults buildMockInvocationResults(List expectationStatements) { - final MockInvocationResults resultWrapper = new MockInvocationResults(); - for (int i = 1; i < expectationStatements.size(); i++) { - Statement expectationStatement = expectationStatements.get(i); - if (expectationStatement instanceof J.MethodInvocation) { - // handle returns statement - J.MethodInvocation invocation = (J.MethodInvocation) expectationStatement; - for (Expression argument : invocation.getArguments()) { - resultWrapper.addResult(argument); - } - continue; - } - J.Assignment assignment = (J.Assignment) expectationStatement; - String variableName = getVariableNameFromAssignment(assignment); - if (variableName == null) { - // unhandled assignment variable type - return null; - } - switch (variableName) { - case "result": - resultWrapper.addResult(assignment.getAssignment()); - break; - case "times": - resultWrapper.setTimes(assignment.getAssignment()); - break; - case "minTimes": - resultWrapper.setMinTimes(assignment.getAssignment()); - break; - case "maxTimes": - resultWrapper.setMaxTimes(assignment.getAssignment()); - break; - } - } - return resultWrapper; - } - - private static String getVariableNameFromAssignment(J.Assignment assignment) { - String name = null; - if (assignment.getVariable() instanceof J.Identifier) { - name = ((J.Identifier) assignment.getVariable()).getSimpleName(); - } else if (assignment.getVariable() instanceof J.FieldAccess) { - J.FieldAccess fieldAccess = (J.FieldAccess) assignment.getVariable(); - if (fieldAccess.getTarget() instanceof J.Identifier) { - name = fieldAccess.getSimpleName(); - } - } - return name; - } - - private static String getPrimitiveTemplateField(JavaType.Primitive primitiveType) { - switch (primitiveType) { - case Boolean: - return "#{any(boolean)}"; - case Byte: - return "#{any(byte)}"; - case Char: - return "#{any(char)}"; - case Double: - return "#{any(double)}"; - case Float: - return "#{any(float)}"; - case Int: - return "#{any(int)}"; - case Long: - return "#{any(long)}"; - case Short: - return "#{any(short)}"; - case String: - return "#{any(String)}"; - case Null: - return "#{any()}"; - default: - return null; - } - } - - private static String getInvocationSelectFullyQualifiedClassName(J.MethodInvocation invocation) { - Expression select = invocation.getSelect(); - if (select == null || select.getType() == null) { - return null; - } - String fqn = null; - if (select.getType() instanceof JavaType.FullyQualified) { - fqn = ((JavaType.FullyQualified) select.getType()).getFullyQualifiedName(); - } - return fqn; - } - - private static class MockInvocationResults { - private final List results = new ArrayList<>(); - private Expression times; - private Expression minTimes; - private Expression maxTimes; - - private List getResults() { - return results; - } - - private void addResult(Expression result) { - results.add(result); - } - - private Expression getTimes() { - return times; - } - - private void setTimes(Expression times) { - this.times = times; - } - - private Expression getMinTimes() { - return minTimes; - } - - private void setMinTimes(Expression minTimes) { - this.minTimes = minTimes; - } - - private Expression getMaxTimes() { - return maxTimes; - } - - private void setMaxTimes(Expression maxTimes) { - this.maxTimes = maxTimes; - } - } -} diff --git a/src/main/java/org/openrewrite/java/testing/jmockit/JMockitAnnotatedArgumentToMockito.java b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitAnnotatedArgumentToMockito.java new file mode 100644 index 000000000..0bc1fe9dd --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitAnnotatedArgumentToMockito.java @@ -0,0 +1,106 @@ +/* + * Copyright 2024 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.jmockit; + +import lombok.EqualsAndHashCode; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +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; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.Statement; + +import java.util.ArrayList; +import java.util.List; + +@EqualsAndHashCode(callSuper = false) +public class JMockitAnnotatedArgumentToMockito extends Recipe { + @Override + public String getDisplayName() { + return "Convert JMockit `@Mocked` and `@Injectable` annotated arguments"; + } + + @Override + public String getDescription() { + return "Convert JMockit `@Mocked` and `@Injectable` annotated arguments into Mockito statements."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check( + Preconditions.or( + new UsesType<>("mockit.Mocked", false), + new UsesType<>("mockit.Injectable", false) + ), + new JavaIsoVisitor() { + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDeclaration, ExecutionContext ctx) { + J.MethodDeclaration md = super.visitMethodDeclaration(methodDeclaration, ctx); + + List parameters = md.getParameters(); + if (!parameters.isEmpty() && !(parameters.get(0) instanceof J.Empty)) { + maybeRemoveImport("mockit.Injectable"); + maybeRemoveImport("mockit.Mocked"); + maybeAddImport("org.mockito.Mockito"); + + // Create lists to store the mocked parameters and the new type parameters + List mockedParameter = new ArrayList<>(); + + // Remove any mocked parameters from the method declaration + md = md.withParameters(ListUtils.map(parameters, parameter -> { + if (parameter instanceof J.VariableDeclarations) { + J.VariableDeclarations variableDeclarations = (J.VariableDeclarations) parameter; + // Check if the parameter has the annotation "mockit.Mocked or mockit.Injectable" + if (!FindAnnotations.find(variableDeclarations, "mockit.Injectable").isEmpty() || + !FindAnnotations.find(variableDeclarations, "mockit.Mocked").isEmpty() ) { + mockedParameter.add(variableDeclarations); + return null; + } + } + return parameter; + })); + + // 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")) + .imports("org.mockito.Mockito") + .contextSensitive() + .build(); + // Retain argument order by iterating in reverse + for (int i = mockedParameter.size() - 1; i >= 0; i--) { + J.VariableDeclarations variableDeclarations = mockedParameter.get(i); + // Apply the template and update the method declaration + md = addStatementsTemplate.apply(updateCursor(md), + md.getBody().getCoordinates().firstStatement(), + variableDeclarations.getTypeExpression().toString(), + variableDeclarations.getVariables().get(0).getSimpleName(), + variableDeclarations.getTypeExpression().toString()); + } + } + } + return md; + } + } + ); + } +} diff --git a/src/main/java/org/openrewrite/java/testing/jmockit/JMockitBlockRewriter.java b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitBlockRewriter.java new file mode 100644 index 000000000..3856b476e --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitBlockRewriter.java @@ -0,0 +1,461 @@ +/* + * Copyright 2024 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.jmockit; + +import lombok.AccessLevel; +import lombok.Data; +import lombok.Setter; +import org.jspecify.annotations.Nullable; +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.tree.*; + +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; + +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("; + 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 String getObjectTemplateField(String fqn) { + return "#{any(" + fqn + ")}"; + } + + private final JavaVisitor visitor; + private final ExecutionContext ctx; + private final J.NewClass newExpectations; + private final JMockitBlockType blockType; + // index of the Expectations block in the method body + private final int bodyStatementIndex; + private J.Block methodBody; + private JavaCoordinates nextStatementCoordinates; + + private boolean rewriteFailed = false; + + boolean isRewriteFailed() { + return rewriteFailed; + } + + // keep track of the additional statements being added to the method body, which impacts the statement indices + // used with bodyStatementIndex to obtain the coordinates of the next statement to be written + private int numStatementsAdded = 0; + + JMockitBlockRewriter(JavaVisitor visitor, ExecutionContext ctx, J.Block methodBody, + J.NewClass newExpectations, int bodyStatementIndex, JMockitBlockType blockType) { + this.visitor = visitor; + this.ctx = ctx; + this.methodBody = methodBody; + this.newExpectations = newExpectations; + this.bodyStatementIndex = bodyStatementIndex; + this.blockType = blockType; + this.nextStatementCoordinates = newExpectations.getCoordinates().replace(); + } + + J.Block rewriteMethodBody() { + visitor.maybeRemoveImport(blockType.getFqn()); // eg mockit.Expectations + + assert newExpectations.getBody() != null; + J.Block jmockitBlock = (J.Block) newExpectations.getBody().getStatements().get(0); + if (jmockitBlock.getStatements().isEmpty()) { + // empty Expectations block, remove it + removeBlock(); + return methodBody; + } + + // rewrite the argument matchers in the expectations block + ArgumentMatchersRewriter amr = new ArgumentMatchersRewriter(visitor, ctx, jmockitBlock); + jmockitBlock = amr.rewriteJMockitBlock(); + + // iterate over the statements and build a list of grouped method invocations and related statements eg times + List> methodInvocationsToRewrite = new ArrayList<>(); + List uniqueMocks = new ArrayList<>(); + int methodInvocationIdx = -1; + for (Statement jmockitBlockStatement : jmockitBlock.getStatements()) { + if (jmockitBlockStatement instanceof J.MethodInvocation) { + J.MethodInvocation invocation = (J.MethodInvocation) jmockitBlockStatement; + 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); + } + } + + // remove the jmockit block + if (nextStatementCoordinates.isReplacement()) { + removeBlock(); + } + + // 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 statementsToRewrite) { + final MockInvocationResults mockInvocationResults = buildMockInvocationResults(statementsToRewrite); + if (mockInvocationResults == null) { + // invalid block, cannot rewrite + this.rewriteFailed = true; + return; + } + + J.MethodInvocation invocation = (J.MethodInvocation) statementsToRewrite.get(0); + boolean hasResults = !mockInvocationResults.getResults().isEmpty(); + boolean hasTimes = mockInvocationResults.hasAnyTimes(); + if (hasResults) { + rewriteResult(invocation, mockInvocationResults.getResults(), hasTimes); + } + + if (!hasResults && !hasTimes && (this.blockType == JMockitBlockType.Expectations || this.blockType.isVerifications())) { + rewriteVerify(invocation, null, ""); + return; + } + if (mockInvocationResults.getTimes() != null) { + rewriteVerify(invocation, mockInvocationResults.getTimes(), "times"); + } + if (mockInvocationResults.getMinTimes() != null) { + rewriteVerify(invocation, mockInvocationResults.getMinTimes(), "atLeast"); + } + if (mockInvocationResults.getMaxTimes() != null) { + rewriteVerify(invocation, mockInvocationResults.getMaxTimes(), "atMost"); + } + } + + private void removeBlock() { + methodBody = JavaTemplate.builder("") + .javaParser(JavaParser.fromJavaVersion()) + .build() + .apply(new Cursor(visitor.getCursor(), methodBody), nextStatementCoordinates); + setNextStatementCoordinates(0); + } + + private void rewriteResult(J.MethodInvocation invocation, List results, boolean hasTimes) { + boolean lenient = this.blockType == NonStrictExpectations && !hasTimes; + String template = getWhenTemplate(results, lenient); + if (template == null) { + // invalid template, cannot rewrite + this.rewriteFailed = true; + return; + } + + List templateParams = new ArrayList<>(); + templateParams.add(invocation); + templateParams.addAll(results); + rewriteTemplate(template, templateParams, nextStatementCoordinates); + if (this.rewriteFailed) { + return; + } + + setNextStatementCoordinates(++numStatementsAdded); + // do this last making sure rewrite worked and specify onlyifReferenced=false because framework cannot find static + // reference for when method invocation when another static mockit reference is added + visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, "when", false); + if (lenient) { + visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, "lenient"); + } + } + + private void rewriteVerify(J.MethodInvocation invocation, @Nullable Expression times, String verificationMode) { + if (invocation.getSelect() == null) { + // cannot write a verification statement for an invocation without a select field + return; + } + + List templateParams = new ArrayList<>(); + templateParams.add(invocation.getSelect()); + if (times != null) { + templateParams.add(times); + } + templateParams.add(invocation.getName().getSimpleName()); + String verifyTemplate = getVerifyTemplate(invocation.getArguments(), verificationMode, templateParams); + JavaCoordinates verifyCoordinates; + 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(); + } + rewriteTemplate(verifyTemplate, templateParams, verifyCoordinates); + if (this.rewriteFailed) { + return; + } + + if (this.blockType.isVerifications()) { + setNextStatementCoordinates(++numStatementsAdded); // for Expectations, verify statements added to end of method + } + + // do this last making sure rewrite worked and specify onlyifReferenced=false because framework cannot find the + // static reference to verify when another static mockit reference is added + visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, "verify", false); + if (!verificationMode.isEmpty()) { + visitor.maybeAddImport(MOCKITO_IMPORT_FQN_PREFX, verificationMode); + } + } + + private void rewriteFullVerify(List 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(); + return; + } + + // the next statement coordinates are directly after the most recently written statement, calculated by + // subtracting the removed jmockit block + int lastStatementIdx = bodyStatementIndex + numStatementsAdded - 1; + if (lastStatementIdx >= this.methodBody.getStatements().size()) { + this.rewriteFailed = true; + return; + } + + this.nextStatementCoordinates = this.methodBody.getStatements().get(lastStatementIdx).getCoordinates().after(); + } + + private void rewriteTemplate(String template, List templateParams, JavaCoordinates + rewriteCoords) { + int numStatementsBefore = methodBody.getStatements().size(); + methodBody = JavaTemplate.builder(template) + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "mockito-core-3.12")) + .staticImports("org.mockito.Mockito.*") + .build() + .apply( + new Cursor(visitor.getCursor(), methodBody), + rewriteCoords, + templateParams.toArray() + ); + this.rewriteFailed = methodBody.getStatements().size() <= numStatementsBefore; + } + + private @Nullable String getWhenTemplate(List results, boolean lenient) { + boolean buildingResults = false; + StringBuilder templateBuilder = new StringBuilder(); + if (lenient) { + templateBuilder.append(LENIENT_TEMPLATE_PREFIX); + } + templateBuilder.append(WHEN_TEMPLATE_PREFIX); + for (Expression result : results) { + JavaType resultType = result.getType(); + if (result instanceof J.Literal) { + appendToTemplate(templateBuilder, buildingResults, RETURN_TEMPLATE_PREFIX, LITERAL_TEMPLATE_FIELD); + } else if (resultType instanceof JavaType.Primitive) { + String primitiveTemplateField = getPrimitiveTemplateField((JavaType.Primitive) resultType); + if (primitiveTemplateField == null) { + // unhandled primitive type + return null; + } + appendToTemplate(templateBuilder, buildingResults, RETURN_TEMPLATE_PREFIX, primitiveTemplateField); + } else if (TypeUtils.isAssignableTo(Throwable.class.getName(), resultType)) { + appendToTemplate(templateBuilder, buildingResults, THROW_TEMPLATE_PREFIX, ANY_TEMPLATE_FIELD); + } else if (resultType instanceof JavaType.Class) { + appendToTemplate(templateBuilder, buildingResults, RETURN_TEMPLATE_PREFIX, + getObjectTemplateField(((JavaType.Class) resultType).getFullyQualifiedName())); + } else if (resultType instanceof JavaType.Parameterized) { + appendToTemplate(templateBuilder, buildingResults, RETURN_TEMPLATE_PREFIX, + getObjectTemplateField(((JavaType.Parameterized) resultType).getType().getFullyQualifiedName())); + } else { + // unhandled result type + return null; + } + buildingResults = true; + } + templateBuilder.append(");"); + return templateBuilder.toString(); + } + + private static void appendToTemplate(StringBuilder templateBuilder, boolean buildingResults, String templatePrefix, + String templateField) { + if (!buildingResults) { + templateBuilder.append(templatePrefix); + } else { + templateBuilder.append(", "); + } + templateBuilder.append(templateField); + } + + private static String getVerifyTemplate(List arguments, String verificationMode, List templateParams) { + StringBuilder templateBuilder = new StringBuilder(VERIFY_TEMPLATE_PREFIX); // eg verify(object + if (!verificationMode.isEmpty()) { + templateBuilder.append(", ").append(verificationMode).append("(#{any(int)})"); // eg verify(object, times(2) + } + templateBuilder.append(").#{}("); // eg verify(object, times(2)).method( + + if (arguments.isEmpty()) { + templateBuilder.append(");"); // eg verify(object, times(2)).method(); <- no args + return templateBuilder.toString(); + } + + boolean hasArgument = false; + for (Expression argument : arguments) { // eg verify(object, times(2).method(anyLong(), anyInt() + if (argument instanceof J.Empty) { + continue; + } else if (argument instanceof J.Literal) { + templateBuilder.append(((J.Literal) argument).getValueSource()); + } else { + templateBuilder.append(ANY_TEMPLATE_FIELD); + templateParams.add(argument); + } + hasArgument = true; + templateBuilder.append(", "); + } + if (hasArgument) { + templateBuilder.delete(templateBuilder.length() - 2, templateBuilder.length()); + } + templateBuilder.append(");"); // eg verify(object, times(2).method(anyLong(), anyInt()); + return templateBuilder.toString(); + } + + private static @Nullable MockInvocationResults buildMockInvocationResults(List expectationStatements) { + final MockInvocationResults resultWrapper = new MockInvocationResults(); + for (int i = 1; i < expectationStatements.size(); i++) { + Statement expectationStatement = expectationStatements.get(i); + if (expectationStatement instanceof J.MethodInvocation) { + // handle returns statement + J.MethodInvocation invocation = (J.MethodInvocation) expectationStatement; + for (Expression argument : invocation.getArguments()) { + resultWrapper.addResult(argument); + } + continue; + } + J.Assignment assignment = (J.Assignment) expectationStatement; + String variableName = getVariableNameFromAssignment(assignment); + if (variableName == null) { + // unhandled assignment variable type + return null; + } + switch (variableName) { + case "result": + resultWrapper.addResult(assignment.getAssignment()); + break; + case "times": + resultWrapper.setTimes(assignment.getAssignment()); + break; + case "minTimes": + resultWrapper.setMinTimes(assignment.getAssignment()); + break; + case "maxTimes": + resultWrapper.setMaxTimes(assignment.getAssignment()); + break; + } + } + return resultWrapper; + } + + private static @Nullable String getVariableNameFromAssignment(J.Assignment assignment) { + if (assignment.getVariable() instanceof J.Identifier) { + return ((J.Identifier) assignment.getVariable()).getSimpleName(); + } else if (assignment.getVariable() instanceof J.FieldAccess) { + J.FieldAccess fieldAccess = (J.FieldAccess) assignment.getVariable(); + if (fieldAccess.getTarget() instanceof J.Identifier) { + return fieldAccess.getSimpleName(); + } + } + return null; + } + + private static @Nullable String getPrimitiveTemplateField(JavaType.Primitive primitiveType) { + switch (primitiveType) { + case Boolean: + return "#{any(boolean)}"; + case Byte: + return "#{any(byte)}"; + case Char: + return "#{any(char)}"; + case Double: + return "#{any(double)}"; + case Float: + return "#{any(float)}"; + case Int: + return "#{any(int)}"; + case Long: + return "#{any(long)}"; + case Short: + return "#{any(short)}"; + case String: + return "#{any(String)}"; + case Null: + return "#{any()}"; + default: + return null; + } + } + + @Data + private static class MockInvocationResults { + @Setter(AccessLevel.NONE) + private final List results = new ArrayList<>(); + private Expression times; + private Expression minTimes; + private Expression maxTimes; + + private void addResult(Expression result) { + results.add(result); + } + + private boolean hasAnyTimes() { + return times != null || minTimes != null || maxTimes != null; + } + } +} diff --git a/src/main/java/org/openrewrite/java/testing/jmockit/JMockitExpectationsToMockito.java b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitBlockToMockito.java similarity index 58% rename from src/main/java/org/openrewrite/java/testing/jmockit/JMockitExpectationsToMockito.java rename to src/main/java/org/openrewrite/java/testing/jmockit/JMockitBlockToMockito.java index e841aa033..1a41ddc4b 100644 --- a/src/main/java/org/openrewrite/java/testing/jmockit/JMockitExpectationsToMockito.java +++ b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitBlockToMockito.java @@ -26,28 +26,37 @@ 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.getSupportedTypesStr; +import static org.openrewrite.java.testing.jmockit.JMockitBlockType.values; @Value @EqualsAndHashCode(callSuper = false) -public class JMockitExpectationsToMockito extends Recipe { +public class JMockitBlockToMockito extends Recipe { + + private static final String SUPPORTED_TYPES = getSupportedTypesStr(); + @Override public String getDisplayName() { - return "Rewrite JMockit Expectations"; + return "Rewrite JMockit " + SUPPORTED_TYPES; } @Override public String getDescription() { - return "Rewrites JMockit `Expectations` blocks to Mockito statements."; + return "Rewrites JMockit `" + SUPPORTED_TYPES + "` blocks to Mockito statements."; } @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("mockit.Expectations", false), - new RewriteExpectationsVisitor()); + @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 RewriteExpectationsVisitor extends JavaIsoVisitor { + private static class RewriteJMockitBlockVisitor extends JavaIsoVisitor { @Override public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDeclaration, ExecutionContext ctx) { @@ -63,17 +72,19 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDecl int bodyStatementIndex = 0; // iterate over each statement in the method body, find Expectations blocks and rewrite them while (bodyStatementIndex < statements.size()) { - if (!JMockitUtils.isValidExpectationsNewClassStatement(statements.get(bodyStatementIndex))) { - bodyStatementIndex += 1; - continue; - } - ExpectationsBlockRewriter ebr = new ExpectationsBlockRewriter(this, ctx, methodBody, - ((J.NewClass) statements.get(bodyStatementIndex)), bodyStatementIndex); - methodBody = ebr.rewriteMethodBody(); - statements = methodBody.getStatements(); - // if the expectations rewrite failed, skip the next statement - if (ebr.isExpectationsRewriteFailed()) { - bodyStatementIndex += 1; + Statement s = statements.get(bodyStatementIndex); + Optional blockType = JMockitUtils.getJMockitBlock(s); + if (blockType.isPresent()) { + JMockitBlockRewriter blockRewriter = new JMockitBlockRewriter(this, ctx, methodBody, + ((J.NewClass) s), bodyStatementIndex, blockType.get()); + methodBody = blockRewriter.rewriteMethodBody(); + statements = methodBody.getStatements(); + // if the expectations rewrite failed, skip the next statement + if (blockRewriter.isRewriteFailed()) { + bodyStatementIndex++; + } + } else { + bodyStatementIndex++; } } return md.withBody(methodBody); diff --git a/src/main/java/org/openrewrite/java/testing/jmockit/JMockitBlockType.java b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitBlockType.java new file mode 100644 index 000000000..c691d97b6 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitBlockType.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024 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.jmockit; + +import lombok.Getter; + +import java.util.Arrays; + +@Getter +enum JMockitBlockType { + + Expectations, + NonStrictExpectations, + Verifications, + FullVerifications; + + private final String fqn = "mockit." + this.name(); + + boolean isVerifications() { + return this == Verifications || this == FullVerifications; + } + + static String getSupportedTypesStr() { + StringBuilder sb = new StringBuilder(); + Arrays.stream(values()).forEach(value -> sb.append(value).append(", ")); + return sb.substring(0, sb.length() - 2); + } +} diff --git a/src/main/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockito.java b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockito.java new file mode 100644 index 000000000..fb942c504 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockito.java @@ -0,0 +1,532 @@ +/* + * Copyright 2024 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.jmockit; + +import org.openrewrite.*; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.*; +import org.openrewrite.marker.SearchResult; +import org.openrewrite.staticanalysis.LambdaBlockToExpression; +import org.openrewrite.staticanalysis.VariableReferences; + +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; +import static org.openrewrite.java.testing.mockito.MockitoUtils.maybeAddMethodWithAnnotation; +import static org.openrewrite.java.tree.Flag.Private; +import static org.openrewrite.java.tree.Flag.Static; + +public class JMockitMockUpToMockito extends Recipe { + + private static final String JMOCKIT_MOCKUP_IMPORT = "mockit.MockUp"; + private static final String JMOCKIT_MOCK_IMPORT = "mockit.Mock"; + + private static final String MOCKITO_CLASSPATH = "mockito-core-3"; + private static final String MOCKITO_ALL_IMPORT = "org.mockito.Mockito.*"; + private static final String MOCKITO_MATCHER_IMPORT = "org.mockito.ArgumentMatchers.*"; + private static final String MOCKITO_DELEGATEANSWER_IMPORT = "org.mockito.AdditionalAnswers.delegatesTo"; + private static final String MOCKITO_STATIC_PREFIX = "mockStatic"; + private static final String MOCKITO_STATIC_IMPORT = "org.mockito.MockedStatic"; + private static final String MOCKITO_MOCK_PREFIX = "mock"; + private static final String MOCKITO_CONSTRUCTION_PREFIX = "mockCons"; + private static final String MOCKITO_CONSTRUCTION_IMPORT = "org.mockito.MockedConstruction"; + + @Override + public String getDisplayName() { + return "Rewrite JMockit MockUp to Mockito statements"; + } + + @Override + public String getDescription() { + return "Rewrites JMockit `MockUp` blocks to Mockito statements. This recipe will not rewrite private methods in MockUp."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesType<>(JMOCKIT_MOCKUP_IMPORT, false), new JMockitMockUpToMockitoVisitor()); + } + + private static class JMockitMockUpToMockitoVisitor extends JavaIsoVisitor { + private final Map tearDownMocks = new HashMap<>(); + + /** + * Handle at class level because need to handle the case where when there is a MockUp in a setup method, and we + * need to close the migrated mockCons in the teardown, yet the teardown method comes before the setup method + */ + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + // Handle @Before/@BeforeEach mockUp + Set mds = TreeVisitor.collect( + new JavaIsoVisitor() { + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration md, ExecutionContext ctx) { + if (isSetUpMethod(md)) { + return SearchResult.found(md); + } + return super.visitMethodDeclaration(md, ctx); + } + }, + classDecl, + new HashSet<>() + ) + .stream() + .filter(J.MethodDeclaration.class::isInstance) + .map(J.MethodDeclaration.class::cast) + .collect(Collectors.toSet()); + if (mds.isEmpty()) { + return super.visitClassDeclaration(classDecl, ctx); + } + + AtomicReference cdRef = new AtomicReference<>(classDecl); + mds.forEach(md -> md.getBody() + .getStatements() + .stream() + .filter(this::isMockUpStatement) + .map(J.NewClass.class::cast) + .forEach(newClass -> { + String className = ((J.ParameterizedType) newClass.getClazz()).getTypeParameters().get(0).toString(); + + Map mockedMethods = getMockUpMethods(newClass); + + // Add mockStatic field + if (mockedMethods.values().stream().anyMatch(m -> m.getFlags().contains(Static))) { + cdRef.set(JavaTemplate.builder("private MockedStatic #{};") + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, MOCKITO_CLASSPATH)) + .imports(MOCKITO_STATIC_IMPORT) + .staticImports(MOCKITO_ALL_IMPORT) + .build() + .apply( + new Cursor(getCursor().getParentOrThrow(), cdRef.get()), + cdRef.get().getBody().getCoordinates().firstStatement(), + MOCKITO_STATIC_PREFIX + className + )); + J.VariableDeclarations mockField = (J.VariableDeclarations) cdRef.get().getBody().getStatements().get(0); + J.Identifier mockFieldId = mockField.getVariables().get(0).getName(); + tearDownMocks.put(MOCKITO_STATIC_PREFIX + className, mockFieldId); + } + // Add mockConstruction field + if (mockedMethods.values().stream().anyMatch(m -> !m.getFlags().contains(Static))) { + cdRef.set(JavaTemplate.builder("private MockedConstruction #{};") + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, MOCKITO_CLASSPATH)) + .imports(MOCKITO_CONSTRUCTION_IMPORT) + .staticImports(MOCKITO_ALL_IMPORT) + .build() + .apply( + updateCursor(cdRef.get()), + cdRef.get().getBody().getCoordinates().firstStatement(), + MOCKITO_CONSTRUCTION_PREFIX + className + )); + J.VariableDeclarations mockField = (J.VariableDeclarations) cdRef.get().getBody().getStatements().get(0); + J.Identifier mockFieldId = mockField.getVariables().get(0).getName(); + tearDownMocks.put(MOCKITO_CONSTRUCTION_PREFIX + className, mockFieldId); + } + })); + + J.ClassDeclaration cd = maybeAddMethodWithAnnotation(this, cdRef.get(), ctx, true, "tearDown", + "@org.junit.After", + "@After", + "junit-4.13", + "org.junit.After", + ""); + + return super.visitClassDeclaration(cd, ctx); + } + + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDecl, ExecutionContext ctx) { + J.MethodDeclaration md = methodDecl; + if (md.getBody() == null) { + return md; + } + if (isTearDownMethod(md)) { + for (J.Identifier id : tearDownMocks.values()) { + String type = TypeUtils.asFullyQualified(id.getFieldType().getType()).getFullyQualifiedName(); + md = JavaTemplate.builder("#{any(" + type + ")}.closeOnDemand();") + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, MOCKITO_CLASSPATH)) + .imports(MOCKITO_STATIC_IMPORT, MOCKITO_CONSTRUCTION_IMPORT) + .staticImports(MOCKITO_ALL_IMPORT) + .build() + .apply( + updateCursor(md), + md.getBody().getCoordinates().lastStatement(), + id + ); + } + return md; + } + + boolean isBeforeTest = isSetUpMethod(md); + List varDeclarationInTry = new ArrayList<>(); + List mockStaticMethodInTry = new ArrayList<>(); + List mockConstructionMethodInTry = new ArrayList<>(); + List encloseStatements = new ArrayList<>(); + List residualStatements = new ArrayList<>(); + for (Statement statement : md.getBody().getStatements()) { + if (!isMockUpStatement(statement)) { + encloseStatements.add(statement); + continue; + } + + J.NewClass newClass = (J.NewClass) statement; + + // Only discard @Mock method declarations + residualStatements.addAll(newClass + .getBody() + .getStatements() + .stream() + .filter(s -> { + if (s instanceof J.MethodDeclaration) { + return ((J.MethodDeclaration) s).getLeadingAnnotations().stream() + .noneMatch(o -> TypeUtils.isOfClassType(o.getType(), JMOCKIT_MOCK_IMPORT)); + } + return true; + }) + .collect(toList()) + ); + + JavaType mockType = ((J.ParameterizedType) newClass.getClazz()).getTypeParameters().get(0).getType(); + String className = ((J.ParameterizedType) newClass.getClazz()).getTypeParameters().get(0).toString(); + + Map mockedMethods = getMockUpMethods(newClass); + + // Add MockStatic + Map mockedPublicStaticMethods = mockedMethods + .entrySet() + .stream() + .filter(m -> m.getValue().getFlags().contains(Static)) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + if (!mockedPublicStaticMethods.isEmpty()) { + if (isBeforeTest) { + String tpl = getMockStaticDeclarationInBefore(className) + + getMockStaticMethods((JavaType.Class) mockType, className, mockedPublicStaticMethods); + + md = JavaTemplate.builder(tpl) + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, MOCKITO_CLASSPATH)) + .imports(MOCKITO_STATIC_IMPORT) + .staticImports(MOCKITO_ALL_IMPORT) + .build() + .apply( + updateCursor(md), + statement.getCoordinates().after(), + tearDownMocks.get(MOCKITO_STATIC_PREFIX + className) + ); + } else { + varDeclarationInTry.add(getMockStaticDeclarationInTry(className)); + mockStaticMethodInTry.add(getMockStaticMethods((JavaType.Class) mockType, className, mockedPublicStaticMethods)); + } + + maybeAddImport(MOCKITO_STATIC_IMPORT); + } + + // Add MockConstruction + Map mockedPublicMethods = mockedMethods + .entrySet() + .stream() + .filter(m -> !m.getValue().getFlags().contains(Static)) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + if (!mockedPublicMethods.isEmpty()) { + if (isBeforeTest) { + String tpl = getMockConstructionMethods(className, mockedPublicMethods) + + getMockConstructionDeclarationInBefore(className); + + md = JavaTemplate.builder(tpl) + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, MOCKITO_CLASSPATH)) + .imports(MOCKITO_STATIC_IMPORT) + .staticImports(MOCKITO_ALL_IMPORT, MOCKITO_DELEGATEANSWER_IMPORT) + .build() + .apply( + updateCursor(md), + statement.getCoordinates().after(), + tearDownMocks.get(MOCKITO_CONSTRUCTION_PREFIX + className) + ); + } else { + varDeclarationInTry.add(getMockConstructionDeclarationInTry(className)); + mockConstructionMethodInTry.add(getMockConstructionMethods(className, mockedPublicMethods)); + } + + maybeAddImport(MOCKITO_CONSTRUCTION_IMPORT); + maybeAddImport("org.mockito.Answers", "CALLS_REAL_METHODS", false); + maybeAddImport("org.mockito.AdditionalAnswers", "delegatesTo", false); + } + + List statements = md.getBody().getStatements(); + statements.remove(statement); + md = md.withBody(md.getBody().withStatements(statements)); + } + + if (!varDeclarationInTry.isEmpty()) { + String tpl = String.join("", mockConstructionMethodInTry) + + "try (" + + String.join(";", varDeclarationInTry) + + ") {" + + String.join(";", mockStaticMethodInTry) + + "}"; + + J.MethodDeclaration residualMd = md.withBody(md.getBody().withStatements(residualStatements)); + residualMd = JavaTemplate.builder(tpl) + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, MOCKITO_CLASSPATH)) + .imports(MOCKITO_STATIC_IMPORT, MOCKITO_CONSTRUCTION_IMPORT) + .staticImports(MOCKITO_ALL_IMPORT, MOCKITO_MATCHER_IMPORT, MOCKITO_MATCHER_IMPORT, MOCKITO_DELEGATEANSWER_IMPORT) + .build() + .apply(updateCursor(residualMd), residualMd.getBody().getCoordinates().lastStatement()); + + List mdStatements = residualMd.getBody().getStatements(); + J.Try try_ = (J.Try) mdStatements.get(mdStatements.size() - 1); + + List tryStatements = try_.getBody().getStatements(); + tryStatements.addAll(encloseStatements); + try_ = try_.withBody(try_.getBody().withStatements(tryStatements)); + + mdStatements.set(mdStatements.size() - 1, try_); + md = md.withBody(residualMd.getBody().withStatements(mdStatements)); + } + + maybeAddImport(MOCKITO_ALL_IMPORT.replace(".*", ""), "*", false); + maybeRemoveImport(JMOCKIT_MOCK_IMPORT); + maybeRemoveImport(JMOCKIT_MOCKUP_IMPORT); + + doAfterVisit(new LambdaBlockToExpression().getVisitor()); + return maybeAutoFormat(methodDecl, md, ctx); + } + + private String getMatcher(JavaType s) { + maybeAddImport(MOCKITO_MATCHER_IMPORT.replace(".*", ""), "*", false); + if (s instanceof JavaType.Primitive) { + switch (s.toString()) { + case "int": + return "anyInt()"; + case "long": + return "anyLong()"; + case "double": + return "anyDouble()"; + case "float": + return "anyFloat()"; + case "short": + return "anyShort()"; + case "byte": + return "anyByte()"; + case "char": + return "anyChar()"; + case "boolean": + return "anyBoolean()"; + } + } else if (s instanceof JavaType.Array) { + String elem = TypeUtils.asArray(s).getElemType().toString(); + return "nullable(" + elem + "[].class)"; + } + return "nullable(" + TypeUtils.asFullyQualified(s).getClassName() + ".class)"; + } + + private String getAnswerBody(J.MethodDeclaration md) { + Set usedVariables = new HashSet<>(); + new JavaIsoVisitor>() { + @Override + public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations.NamedVariable variable, Set ctx) { + Cursor scope = getCursor().dropParentUntil((is) -> is instanceof J.ClassDeclaration || is instanceof J.Block || is instanceof J.MethodDeclaration || is instanceof J.ForLoop || is instanceof J.ForEachLoop || is instanceof J.ForLoop.Control || is instanceof J.ForEachLoop.Control || is instanceof J.Case || is instanceof J.Try || is instanceof J.Try.Resource || is instanceof J.Try.Catch || is instanceof J.MultiCatch || is instanceof J.Lambda || is instanceof JavaSourceFile); + if (!VariableReferences.findRhsReferences(scope.getValue(), variable.getName()).isEmpty()) { + ctx.add(variable.getSimpleName()); + } + return super.visitVariable(variable, ctx); + } + }.visit(md, usedVariables); + + StringBuilder sb = new StringBuilder(); + List parameters = md.getParameters(); + for (int i = 0; i < parameters.size(); i++) { + if (!(parameters.get(i) instanceof J.VariableDeclarations)) { + continue; + } + J.VariableDeclarations vd = (J.VariableDeclarations) parameters.get(i); + String className; + if (vd.getType() instanceof JavaType.Primitive) { + className = vd.getType().toString(); + } else { + className = vd.getTypeAsFullyQualified().getClassName(); + } + String varName = vd.getVariables().get(0).getName().getSimpleName(); + if (usedVariables.contains(varName)) { + sb.append(className).append(" ").append(varName) + .append(" = invocation.getArgument(").append(i).append(");"); + } + } + + boolean hasReturn = false; + for (Statement s : md.getBody().getStatements()) { + hasReturn |= s instanceof J.Return; + sb.append(s.print(getCursor())).append(";"); + } + // Avoid syntax error + if (!hasReturn) { + sb.append("return null;"); + } + return sb.toString(); + } + + private String getCallRealMethod(JavaType.Method m) { + return "(" + + m.getParameterTypes() + .stream() + .map(this::getMatcher) + .collect(Collectors.joining(", ")) + + ")).thenCallRealMethod();"; + } + + private String getMockStaticDeclarationInBefore(String className) { + return "#{any(" + MOCKITO_STATIC_IMPORT + ")}" + + " = mockStatic(" + className + ".class);"; + } + + private String getMockStaticDeclarationInTry(String className) { + return "MockedStatic " + MOCKITO_STATIC_PREFIX + className + + " = mockStatic(" + className + ".class)"; + } + + private String getMockStaticMethods(JavaType.Class clazz, String className, Map mockedMethods) { + StringBuilder tpl = new StringBuilder(); + + // To generate predictable method order + List keys = mockedMethods.keySet().stream() + .sorted(Comparator.comparing(o -> o.print(getCursor()))) + .collect(toList()); + for (J.MethodDeclaration m : keys) { + tpl.append("mockStatic").append(className) + .append(".when(() -> ").append(className).append(".").append(m.getSimpleName()).append("(") + .append(m.getParameters() + .stream() + .filter(J.VariableDeclarations.class::isInstance) + .map(J.VariableDeclarations.class::cast) + .map(J.VariableDeclarations::getType) + .map(this::getMatcher) + .collect(Collectors.joining(", ")) + ) + .append(")).thenAnswer(invocation -> {") + .append(getAnswerBody(m)) + .append("});"); + } + + // Call real method for non private, static methods + clazz.getMethods() + .stream() + .filter(m -> !m.isConstructor()) + .filter(m -> !m.getFlags().contains(Private)) + .filter(m -> m.getFlags().contains(Static)) + .filter(m -> !mockedMethods.containsValue(m)) + .forEach(m -> tpl.append("mockStatic").append(className).append(".when(() -> ") + .append(className).append(".").append(m.getName()) + .append(getCallRealMethod(m)) + .append(");") + ); + + return tpl.toString(); + } + + private String getMockConstructionDeclarationInBefore(String className) { + return "#{any(" + MOCKITO_CONSTRUCTION_IMPORT + ")}" + + " = mockConstructionWithAnswer(" + className + ".class, delegatesTo(" + MOCKITO_MOCK_PREFIX + className + "));"; + } + + private String getMockConstructionDeclarationInTry(String className) { + return "MockedConstruction " + MOCKITO_CONSTRUCTION_PREFIX + className + + " = mockConstructionWithAnswer(" + className + ".class, delegatesTo(" + MOCKITO_MOCK_PREFIX + className + "))"; + } + + private String getMockConstructionMethods(String className, Map mockedMethods) { + StringBuilder tpl = new StringBuilder() + .append(className) + .append(" ") + .append(MOCKITO_MOCK_PREFIX).append(className) + .append(" = mock(").append(className).append(".class, CALLS_REAL_METHODS);"); + + mockedMethods + .keySet() + .stream() + .sorted(Comparator.comparing(o -> o.print(getCursor()))) + .forEach(m -> tpl.append("doAnswer(invocation -> {") + .append(getAnswerBody(m)) + .append("}).when(").append(MOCKITO_MOCK_PREFIX).append(className).append(").").append(m.getSimpleName()).append("(") + .append(m.getParameters() + .stream() + .filter(J.VariableDeclarations.class::isInstance) + .map(J.VariableDeclarations.class::cast) + .map(J.VariableDeclarations::getType) + .map(this::getMatcher) + .collect(Collectors.joining(", ")) + ) + .append(");")); + + return tpl.toString(); + } + + private boolean isMockUpStatement(Tree tree) { + return tree instanceof J.NewClass && + ((J.NewClass) tree).getClazz() != null && + TypeUtils.isOfClassType(((J.NewClass) tree).getClazz().getType(), JMOCKIT_MOCKUP_IMPORT); + } + + private boolean isSetUpMethod(J.MethodDeclaration md) { + return md + .getLeadingAnnotations() + .stream() + .anyMatch(o -> TypeUtils.isOfClassType(o.getType(), "org.junit.Before")); + } + + private boolean isTearDownMethod(J.MethodDeclaration md) { + return md + .getLeadingAnnotations() + .stream() + .anyMatch(o -> TypeUtils.isOfClassType(o.getType(), "org.junit.After")); + } + + private Map getMockUpMethods(J.NewClass newClass) { + JavaType mockType = ((J.ParameterizedType) newClass.getClazz()).getTypeParameters().get(0).getType(); + return newClass.getBody() + .getStatements() + .stream() + .filter(J.MethodDeclaration.class::isInstance) + .map(J.MethodDeclaration.class::cast) + .filter(s -> s.getLeadingAnnotations().stream() + .anyMatch(o -> TypeUtils.isOfClassType(o.getType(), JMOCKIT_MOCK_IMPORT))) + .map(method -> { + Optional found = TypeUtils.findDeclaredMethod( + TypeUtils.asFullyQualified(mockType), + method.getSimpleName(), + method.getMethodType().getParameterTypes() + ); + if (found.isPresent()) { + JavaType.Method m = found.get(); + if (!m.getFlags().contains(Private)) { + return new AbstractMap.SimpleEntry<>(method, found.get()); + } + } + return null; + }) + .filter(Objects::nonNull) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + } +} diff --git a/src/main/java/org/openrewrite/java/testing/jmockit/JMockitMockedVariableToMockito.java b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitMockedVariableToMockito.java deleted file mode 100644 index 80e7ce019..000000000 --- a/src/main/java/org/openrewrite/java/testing/jmockit/JMockitMockedVariableToMockito.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2024 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.jmockit; - -import lombok.EqualsAndHashCode; -import lombok.Value; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Preconditions; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; -import org.openrewrite.internal.ListUtils; -import org.openrewrite.internal.lang.NonNullApi; -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; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.Statement; - -import java.util.ArrayList; -import java.util.List; - -@Value -@EqualsAndHashCode(callSuper = false) -@NonNullApi -public class JMockitMockedVariableToMockito extends Recipe { - @Override - public String getDisplayName() { - return "Rewrite JMockit Mocked Variable"; - } - - @Override - public String getDescription() { - return "Rewrites JMockit `Mocked Variable` to Mockito statements."; - } - - @Override - public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("mockit.Mocked", false), - new RewriteMockedVariableVisitor()); - } - - private static class RewriteMockedVariableVisitor extends JavaIsoVisitor { - @Override - public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDeclaration, ExecutionContext ctx) { - J.MethodDeclaration md = super.visitMethodDeclaration(methodDeclaration, ctx); - - List parameters = md.getParameters(); - if (!parameters.isEmpty() && !(parameters.get(0) instanceof J.Empty)) { - maybeRemoveImport("mockit.Mocked"); - maybeAddImport("org.mockito.Mockito"); - - // Create lists to store the mocked parameters and the new type parameters - List mockedParameter = new ArrayList<>(); - - // Remove any mocked parameters from the method declaration - md = md.withParameters(ListUtils.map(parameters, parameter -> { - if (parameter instanceof J.VariableDeclarations) { - J.VariableDeclarations variableDeclarations = (J.VariableDeclarations) parameter; - // Check if the parameter has the annotation "mockit.Mocked" - if (!FindAnnotations.find(variableDeclarations, "mockit.Mocked").isEmpty()) { - mockedParameter.add(variableDeclarations); - return null; - } - } - return parameter; - })); - - // 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")) - .imports("org.mockito.Mockito") - .contextSensitive() - .build(); - // Retain argument order by iterating in reverse - for (int i = mockedParameter.size() - 1; i >= 0; i--) { - J.VariableDeclarations variableDeclarations = mockedParameter.get(i); - // Apply the template and update the method declaration - md = addStatementsTemplate.apply(updateCursor(md), - md.getBody().getCoordinates().firstStatement(), - variableDeclarations.getTypeExpression().toString(), - variableDeclarations.getVariables().get(0).getSimpleName(), - variableDeclarations.getTypeExpression().toString()); - } - } - } - - return md; - } - } -} diff --git a/src/main/java/org/openrewrite/java/testing/jmockit/JMockitUtils.java b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitUtils.java index 79d21e91c..a362882b8 100644 --- a/src/main/java/org/openrewrite/java/testing/jmockit/JMockitUtils.java +++ b/src/main/java/org/openrewrite/java/testing/jmockit/JMockitUtils.java @@ -16,23 +16,34 @@ package org.openrewrite.java.testing.jmockit; import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.Statement; import org.openrewrite.java.tree.TypeUtils; +import java.util.Arrays; +import java.util.Optional; + +import static java.util.Optional.empty; + class JMockitUtils { - static boolean isValidExpectationsNewClassStatement(Statement s) { + + static Optional getJMockitBlock(Statement s) { if (!(s instanceof J.NewClass)) { - return false; + return empty(); } + J.NewClass nc = (J.NewClass) s; - if (!(nc.getClazz() instanceof J.Identifier)) { - return false; + if (nc.getBody() == null || nc.getClazz() == null) { + return empty(); } - J.Identifier clazz = (J.Identifier) nc.getClazz(); - if (!TypeUtils.isAssignableTo("mockit.Expectations", clazz.getType())) { - return false; + + JavaType type = nc.getClazz().getType(); + if (type == null) { + return empty(); } - // Expectations block should be composed of a block within another block - return nc.getBody() != null && nc.getBody().getStatements().size() == 1; + + return Arrays.stream(JMockitBlockType.values()) + .filter(supportedType -> TypeUtils.isOfClassType(type, supportedType.getFqn())) + .findFirst(); } } diff --git a/src/main/java/org/openrewrite/java/testing/jmockit/SetupStatementsRewriter.java b/src/main/java/org/openrewrite/java/testing/jmockit/SetupStatementsRewriter.java index 4fd9a54f2..473f82be4 100644 --- a/src/main/java/org/openrewrite/java/testing/jmockit/SetupStatementsRewriter.java +++ b/src/main/java/org/openrewrite/java/testing/jmockit/SetupStatementsRewriter.java @@ -36,9 +36,9 @@ class SetupStatementsRewriter { J.Block rewriteMethodBody() { List statements = methodBody.getStatements(); - // 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 for (Statement s : statements) { - if (!JMockitUtils.isValidExpectationsNewClassStatement(s)) { + if (!JMockitUtils.getJMockitBlock(s).isPresent()) { continue; } J.NewClass nc = (J.NewClass) s; @@ -52,18 +52,30 @@ J.Block rewriteMethodBody() { assert nc.getBody() != null; J.Block expectationsBlock = (J.Block) nc.getBody().getStatements().get(0); + // Account for Expectations which may contain multiple blocks + List statementList = new ArrayList<>(); + if (TypeUtils.isAssignableTo("mockit.Expectations", nc.getType()) || + TypeUtils.isAssignableTo("mockit.Verifications", nc.getType())) { + statementList.addAll(nc.getBody().getStatements()); + } else { + statementList.add(expectationsBlock); + } + // statement needs to be moved directly before expectations class instantiation JavaCoordinates coordinates = nc.getCoordinates().before(); List newExpectationsBlockStatements = new ArrayList<>(); - for (Statement expectationStatement : expectationsBlock.getStatements()) { - if (!isSetupStatement(expectationStatement, spies)) { - newExpectationsBlockStatements.add(expectationStatement); - continue; + for (Statement st : statementList) { + for (Statement expectationStatement : ((J.Block) st).getStatements()) { + if (!isSetupStatement(expectationStatement, spies)) { + newExpectationsBlockStatements.add(expectationStatement); + continue; + } + rewriteBodyStatement(expectationStatement, coordinates); + // subsequent setup statements are moved in order + coordinates = expectationStatement.getCoordinates().after(); } - rewriteBodyStatement(expectationStatement, coordinates); - // subsequent setup statements are moved in order - coordinates = expectationStatement.getCoordinates().after(); } + // the new expectations block has the setup statements removed J.Block newExpectationsBlock = expectationsBlock.withStatements(newExpectationsBlockStatements); nc = nc.withBody(nc.getBody().withStatements(Collections.singletonList(newExpectationsBlock))); @@ -127,8 +139,8 @@ private static boolean isNotMockIdentifier(J.Identifier identifier, Set if (spies.contains(identifier.getSimpleName())) { return false; } - if (identifier.getType() instanceof JavaType.Method - && TypeUtils.isAssignableTo("mockit.Invocations", + if (identifier.getType() instanceof JavaType.Method && + TypeUtils.isAssignableTo("mockit.Invocations", ((JavaType.Method) identifier.getType()).getDeclaringType())) { return false; } @@ -137,9 +149,9 @@ private static boolean isNotMockIdentifier(J.Identifier identifier, Set return true; } for (JavaType.FullyQualified annotationType : fieldType.getAnnotations()) { - if (TypeUtils.isAssignableTo("mockit.Mocked", annotationType) - || TypeUtils.isAssignableTo("mockit.Injectable", annotationType) - || TypeUtils.isAssignableTo("mockit.Tested", annotationType)) { + if (TypeUtils.isAssignableTo("mockit.Mocked", annotationType) || + TypeUtils.isAssignableTo("mockit.Injectable", annotationType) || + TypeUtils.isAssignableTo("mockit.Tested", annotationType)) { return false; } } diff --git a/src/main/java/org/openrewrite/java/testing/jmockit/package-info.java b/src/main/java/org/openrewrite/java/testing/jmockit/package-info.java new file mode 100644 index 000000000..af3338fa5 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/jmockit/package-info.java @@ -0,0 +1,19 @@ +/* + * 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. + */ +@NullMarked +package org.openrewrite.java.testing.jmockit; + +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/openrewrite/java/testing/junit5/AddHamcrestJUnitDependency.java b/src/main/java/org/openrewrite/java/testing/junit5/AddHamcrestJUnitDependency.java new file mode 100644 index 000000000..915d17ec1 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/junit5/AddHamcrestJUnitDependency.java @@ -0,0 +1,89 @@ +/* + * Copyright 2024 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.junit5; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.ScanningRecipe; +import org.openrewrite.Tree; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.dependencies.AddDependency; +import org.openrewrite.java.tree.JavaSourceFile; +import org.openrewrite.java.tree.JavaType; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class AddHamcrestJUnitDependency extends ScanningRecipe { + + @Override + public String getDisplayName() { + return "Add Hamcrest JUnit dependency"; + } + + @Override + public String getDescription() { + return "Add Hamcrest JUnit dependency only if JUnit 4's `assertThat` or `assumeThat` is used."; + } + + @Override + public AtomicBoolean getInitialValue(ExecutionContext ctx) { + return new AtomicBoolean(false); + } + + @Override + public TreeVisitor getScanner(AtomicBoolean acc) { + // No need to scan for AddDependency, as we'll unconditionally add the dependency if we find a match below + MethodMatcher methodMatcher = new MethodMatcher("org.junit.Ass* *That(..)"); + return new TreeVisitor() { + @Override + public Tree preVisit(Tree tree, ExecutionContext ctx) { + stopAfterPreVisit(); + if (tree instanceof JavaSourceFile && !acc.get()) { + for (JavaType.Method type : ((JavaSourceFile) tree).getTypesInUse().getUsedMethods()) { + if (methodMatcher.matches(type)) { + acc.set(true); + } + } + } + return tree; + } + }; + } + + @Override + public TreeVisitor getVisitor(AtomicBoolean acc) { + if (acc.get()) { + // We can unconditionally add the dependency here + return new AddDependency( + "org.hamcrest", + "hamcrest-junit", + "2.x", + null, + null, + null, + null, + null, + null, + "test", + null, + null, + null, + true + ).getVisitor(); + } + return TreeVisitor.noop(); + } +} diff --git a/src/main/java/org/openrewrite/java/testing/junit5/AddJupiterDependencies.java b/src/main/java/org/openrewrite/java/testing/junit5/AddJupiterDependencies.java index 928a7646f..0c9365021 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/AddJupiterDependencies.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/AddJupiterDependencies.java @@ -17,16 +17,15 @@ import lombok.EqualsAndHashCode; import lombok.Value; +import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.gradle.marker.GradleDependencyConfiguration; import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.groovy.GroovyIsoVisitor; import org.openrewrite.groovy.tree.G; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.dependencies.AddDependency; import org.openrewrite.maven.MavenIsoVisitor; import org.openrewrite.maven.tree.ResolvedDependency; -import org.openrewrite.maven.tree.Scope; import org.openrewrite.xml.tree.Xml; import java.util.List; @@ -64,14 +63,14 @@ public TreeVisitor getVisitor(AddDependency.Accumulator acc return new TreeVisitor() { @Override public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx, Cursor parent) { - if(!(tree instanceof SourceFile)) { + if (!(tree instanceof SourceFile)) { return tree; } SourceFile s = (SourceFile) tree; - if(gv.isAcceptable(s, ctx)) { + if (gv.isAcceptable(s, ctx)) { s = (SourceFile) gv.visitNonNull(s, ctx); } - if(mv.isAcceptable(s, ctx)) { + if (mv.isAcceptable(s, ctx)) { s = (SourceFile) mv.visitNonNull(s, ctx); } return s; @@ -81,7 +80,7 @@ public TreeVisitor getVisitor(AddDependency.Accumulator acc private static AddDependency addJupiterDependency() { return new AddDependency("org.junit.jupiter", "junit-jupiter", "5.x", null, - "org.junit..*", null, null, null, null, "test", + "org.junit..*", null, null, null, null, null, null, null, null, null); } @@ -93,16 +92,16 @@ private static class AddJupiterGradle extends GroovyIsoVisitor @Override public G.CompilationUnit visitCompilationUnit(G.CompilationUnit t, ExecutionContext ctx) { Optional maybeGp = t.getMarkers().findFirst(GradleProject.class); - if(!maybeGp.isPresent()) { + if (!maybeGp.isPresent()) { return t; } GradleProject gp = maybeGp.get(); GradleDependencyConfiguration trc = gp.getConfiguration("testRuntimeClasspath"); - if(trc == null) { + if (trc == null) { return t; } ResolvedDependency jupiterApi = trc.findResolvedDependency("org.junit.jupiter", "junit-jupiter-api"); - if(jupiterApi == null) { + if (jupiterApi == null) { t = (G.CompilationUnit) addJupiterDependency().getVisitor(acc) .visitNonNull(t, ctx); } @@ -115,11 +114,12 @@ public G.CompilationUnit visitCompilationUnit(G.CompilationUnit t, ExecutionCont @EqualsAndHashCode(callSuper = false) private static class AddJupiterMaven extends MavenIsoVisitor { AddDependency.Accumulator acc; + @Override public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { Xml.Document d = document; - List jupiterApi = getResolutionResult().findDependencies("org.junit.jupiter", "junit-jupiter-api", Scope.Test); - if(jupiterApi.isEmpty()) { + List jupiterApi = getResolutionResult().findDependencies("org.junit.jupiter", "junit-jupiter-api", null); + if (jupiterApi.isEmpty()) { d = (Xml.Document) addJupiterDependency().getVisitor(acc) .visitNonNull(d, ctx); } diff --git a/src/main/java/org/openrewrite/java/testing/junit5/AddMissingNested.java b/src/main/java/org/openrewrite/java/testing/junit5/AddMissingNested.java index 547b25a08..e39cccb19 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/AddMissingNested.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/AddMissingNested.java @@ -21,7 +21,6 @@ import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; -import org.openrewrite.internal.lang.NonNull; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; @@ -74,7 +73,7 @@ public TreeVisitor getVisitor() { @Override public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx); - cd = cd.withBody((J.Block) new AddNestedAnnotationVisitor().visitNonNull(cd.getBody(), ctx, getCursor())); + cd = cd.withBody((J.Block) new AddNestedAnnotationVisitor().visitNonNull(cd.getBody(), ctx, updateCursor(cd))); maybeAddImport(NESTED); return cd; } @@ -88,22 +87,18 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex boolean alreadyNested = classDecl.getLeadingAnnotations().stream() .anyMatch(a -> TypeUtils.isOfClassType(a.getType(), NESTED)); if (!alreadyNested && hasTestMethods(cd)) { - cd = getNestedJavaTemplate(ctx).apply(updateCursor(cd), - cd.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName))); + cd = JavaTemplate.builder("@Nested") + .javaParser(JavaParser.fromJavaVersion() + .classpathFromResources(ctx, "junit-jupiter-api-5.9")) + .imports(NESTED) + .build() + .apply(getCursor(), cd.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName))); cd.getModifiers().removeIf(modifier -> modifier.getType().equals(J.Modifier.Type.Static)); + return maybeAutoFormat(classDecl, cd, ctx); } return cd; } - @NonNull - private JavaTemplate getNestedJavaTemplate(ExecutionContext ctx) { - return JavaTemplate.builder("@Nested") - .javaParser(JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "junit-jupiter-api-5.9")) - .imports(NESTED) - .build(); - } - private static boolean hasTestMethods(final J.ClassDeclaration cd) { return TEST_ANNOTATIONS.stream().anyMatch(ann -> !FindAnnotations.find(cd, "@" + ann).isEmpty()); } diff --git a/src/main/java/org/openrewrite/java/testing/junit5/AssertThrowsOnLastStatement.java b/src/main/java/org/openrewrite/java/testing/junit5/AssertThrowsOnLastStatement.java new file mode 100644 index 000000000..add5ab8e7 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/junit5/AssertThrowsOnLastStatement.java @@ -0,0 +1,142 @@ +/* + * 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.junit5; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.JavaIsoVisitor; +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.Statement; +import org.openrewrite.staticanalysis.LambdaBlockToExpression; + +import java.util.Collections; +import java.util.List; + +import static java.util.Collections.singletonList; + + +public class AssertThrowsOnLastStatement extends Recipe { + + @Override + public String getDisplayName() { + return "Applies JUnit 5 `assertThrows` on last statement in lambda block only"; + } + + @Override + public String getDescription() { + return "Applies JUnit 5 `assertThrows` on last statement in lambda block only. " + + "In rare cases may cause compilation errors if the lambda uses effectively non final variables. " + + "In some cases, tests might fail if earlier statements in the lambda block throw exceptions."; + } + + @Override + public TreeVisitor getVisitor() { + MethodMatcher assertThrowsMatcher = new MethodMatcher( + "org.junit.jupiter.api.Assertions assertThrows(java.lang.Class, org.junit.jupiter.api.function.Executable, ..)"); + return Preconditions.check(new UsesMethod<>(assertThrowsMatcher), new JavaIsoVisitor() { + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDecl, ExecutionContext ctx) { + J.MethodDeclaration m = super.visitMethodDeclaration(methodDecl, ctx); + if (m.getBody() == null) { + return m; + } + doAfterVisit(new LambdaBlockToExpression().getVisitor()); + return m.withBody(m.getBody().withStatements(ListUtils.flatMap(m.getBody().getStatements(), methodStatement -> { + J statementToCheck = methodStatement; + final J.VariableDeclarations assertThrowsWithVarDec; + final J.VariableDeclarations.NamedVariable assertThrowsVar; + + if (methodStatement instanceof J.VariableDeclarations) { + assertThrowsWithVarDec = (J.VariableDeclarations) methodStatement; + List assertThrowsNamedVars = assertThrowsWithVarDec.getVariables(); + if (assertThrowsNamedVars.size() != 1) { + return methodStatement; + } + + // has variable declaration for assertThrows eg Throwable ex = assertThrows(....) + assertThrowsVar = assertThrowsNamedVars.get(0); + statementToCheck = assertThrowsVar.getInitializer(); + } else { + assertThrowsWithVarDec = null; + assertThrowsVar = null; + } + + if (!(statementToCheck instanceof J.MethodInvocation)) { + return methodStatement; + } + + J.MethodInvocation methodInvocation = (J.MethodInvocation) statementToCheck; + if (!assertThrowsMatcher.matches(methodInvocation)) { + return methodStatement; + } + + List arguments = methodInvocation.getArguments(); + if (arguments.size() <= 1) { + return methodStatement; + } + + Expression arg = arguments.get(1); + if (!(arg instanceof J.Lambda)) { + return methodStatement; + } + + J.Lambda lambda = (J.Lambda) arg; + if (!(lambda.getBody() instanceof J.Block)) { + return methodStatement; + } + + J.Block body = (J.Block) lambda.getBody(); + List lambdaStatements = body.getStatements(); + if (lambdaStatements.size() <= 1) { + return methodStatement; + } + + // TODO Check to see if last line in lambda does not use a non-final variable + + // move all the statements from the body into before the method invocation, except last one + return ListUtils.map(lambdaStatements, (idx, lambdaStatement) -> { + if (idx < lambdaStatements.size() - 1) { + return lambdaStatement.withPrefix(methodStatement.getPrefix().withComments(Collections.emptyList())); + } + + J.MethodInvocation newAssertThrows = methodInvocation.withArguments( + ListUtils.map(arguments, (argIdx, argument) -> { + if (argIdx == 1) { + // Only retain the last statement in the lambda block + return lambda.withBody(body.withStatements(singletonList(lambdaStatement))); + } + return argument; + }) + ); + + if (assertThrowsWithVarDec == null) { + return newAssertThrows; + } + + J.VariableDeclarations.NamedVariable newAssertThrowsVar = assertThrowsVar.withInitializer(newAssertThrows); + return assertThrowsWithVarDec.withVariables(singletonList(newAssertThrowsVar)); + }); + }))); + } + }); + } +} diff --git a/src/main/java/org/openrewrite/java/testing/junit5/AssertToAssertions.java b/src/main/java/org/openrewrite/java/testing/junit5/AssertToAssertions.java index 497713472..4c9f4caaa 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/AssertToAssertions.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/AssertToAssertions.java @@ -15,12 +15,12 @@ */ package org.openrewrite.java.testing.junit5; +import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.internal.ListUtils; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.ChangeMethodTargetToStatic; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.search.UsesType; diff --git a/src/main/java/org/openrewrite/java/testing/junit5/AssertTrueInstanceofToAssertInstanceOf.java b/src/main/java/org/openrewrite/java/testing/junit5/AssertTrueInstanceofToAssertInstanceOf.java index 30bc12d38..a80e9b5dd 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/AssertTrueInstanceofToAssertInstanceOf.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/AssertTrueInstanceofToAssertInstanceOf.java @@ -99,9 +99,9 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu .staticImports("org.junit.jupiter.api.Assertions.assertInstanceOf") .build(); - J.MethodInvocation methodd = reason != null - ? template.apply(getCursor(), mi.getCoordinates().replace(), clazz.toString(), expression, reason) - : template.apply(getCursor(), mi.getCoordinates().replace(), clazz.toString(), expression); + J.MethodInvocation methodd = reason != null ? + template.apply(getCursor(), mi.getCoordinates().replace(), clazz.toString(), expression, reason) : + template.apply(getCursor(), mi.getCoordinates().replace(), clazz.toString(), expression); maybeAddImport("org.junit.jupiter.api.Assertions", "assertInstanceOf"); return methodd; } diff --git a/src/main/java/org/openrewrite/java/testing/junit5/CleanupJUnitImports.java b/src/main/java/org/openrewrite/java/testing/junit5/CleanupJUnitImports.java index a411e92b2..77a408c1b 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/CleanupJUnitImports.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/CleanupJUnitImports.java @@ -22,6 +22,7 @@ import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaSourceFile; public class CleanupJUnitImports extends Recipe { @Override @@ -44,14 +45,18 @@ public TreeVisitor getVisitor() { public static class CleanupJUnitImportsVisitor extends JavaIsoVisitor { @Override - public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { - for (J.Import im : cu.getImports()) { - String packageName = im.getPackageName(); - if (packageName.startsWith("junit") || (packageName.startsWith("org.junit") && !packageName.contains("jupiter"))) { - maybeRemoveImport(im.getTypeName()); + public J preVisit(J tree, ExecutionContext ctx) { + stopAfterPreVisit(); + if (tree instanceof JavaSourceFile) { + JavaSourceFile c = (JavaSourceFile) tree; + for (J.Import imp : c.getImports()) { + String packageName = imp.getPackageName(); + if (packageName.startsWith("junit") || (packageName.startsWith("org.junit") && !packageName.contains("jupiter"))) { + maybeRemoveImport(imp.getTypeName()); + } } } - return cu; + return tree; } } } diff --git a/src/main/java/org/openrewrite/java/testing/junit5/EnclosedToNested.java b/src/main/java/org/openrewrite/java/testing/junit5/EnclosedToNested.java index d4d54c8f4..ac3ee5573 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/EnclosedToNested.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/EnclosedToNested.java @@ -21,24 +21,18 @@ 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.JavaParser; -import org.openrewrite.java.JavaTemplate; -import org.openrewrite.java.search.FindAnnotations; +import org.openrewrite.java.RemoveAnnotationVisitor; import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.J; -import java.util.Comparator; -import java.util.Set; - @Value @EqualsAndHashCode(callSuper = false) public class EnclosedToNested extends Recipe { private static final String ENCLOSED = "org.junit.experimental.runners.Enclosed"; private static final String RUN_WITH = "org.junit.runner.RunWith"; - private static final String NESTED = "org.junit.jupiter.api.Nested"; - private static final String TEST_JUNIT4 = "org.junit.Test"; - private static final String TEST_JUNIT_JUPITER = "org.junit.jupiter.api.Test"; + private static final String RUN_WITH_ENCLOSED = String.format("@%s(%s.class)", RUN_WITH, ENCLOSED); @Override public String getDisplayName() { @@ -47,54 +41,19 @@ public String getDisplayName() { @Override public String getDescription() { - return "Removes the `Enclosed` specification from a class, and adds `Nested` to its inner classes."; + return "Removes the `Enclosed` specification from a class, with `Nested` added to its inner classes by `AddMissingNested`."; } @Override public TreeVisitor getVisitor() { return Preconditions.check(new UsesType<>(ENCLOSED, false), new JavaIsoVisitor() { - @SuppressWarnings("ConstantConditions") @Override public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx); - final Set runWithEnclosedAnnotationSet = FindAnnotations.find(cd.withBody(null), - String.format("@%s(%s.class)", RUN_WITH, ENCLOSED)); - for (J.Annotation runWithEnclosed : runWithEnclosedAnnotationSet) { - cd.getLeadingAnnotations().remove(runWithEnclosed); - cd = cd.withBody((J.Block) new AddNestedAnnotationVisitor().visit(cd.getBody(), ctx, getCursor())); - - maybeRemoveImport(ENCLOSED); - maybeRemoveImport(RUN_WITH); - maybeAddImport(NESTED); - } - return cd; + maybeRemoveImport(ENCLOSED); + maybeRemoveImport(RUN_WITH); + return (J.ClassDeclaration) new RemoveAnnotationVisitor(new AnnotationMatcher(RUN_WITH_ENCLOSED)).visitNonNull(cd, ctx); } }); } - - public static class AddNestedAnnotationVisitor extends JavaIsoVisitor { - @Override - public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { - J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx); - if (hasTestMethods(cd)) { - cd = getNestedJavaTemplate(ctx).apply(updateCursor(cd), cd.getCoordinates().addAnnotation(Comparator.comparing( - J.Annotation::getSimpleName))); - cd.getModifiers().removeIf(modifier -> modifier.getType().equals(J.Modifier.Type.Static)); - } - return cd; - } - - private JavaTemplate getNestedJavaTemplate(ExecutionContext ctx) { - return JavaTemplate.builder("@Nested") - .javaParser(JavaParser.fromJavaVersion() - .classpathFromResources(ctx, "junit-jupiter-api-5.9")) - .imports(NESTED) - .build(); - } - - private boolean hasTestMethods(final J.ClassDeclaration cd) { - return !FindAnnotations.find(cd, "@" + TEST_JUNIT4).isEmpty() - || !FindAnnotations.find(cd, "@" + TEST_JUNIT_JUPITER).isEmpty(); - } - } } diff --git a/src/main/java/org/openrewrite/java/testing/junit5/ExpectedExceptionToAssertThrows.java b/src/main/java/org/openrewrite/java/testing/junit5/ExpectedExceptionToAssertThrows.java index f4e66f5fb..78a5d3fe5 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/ExpectedExceptionToAssertThrows.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/ExpectedExceptionToAssertThrows.java @@ -15,18 +15,20 @@ */ package org.openrewrite.java.testing.junit5; +import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.internal.ListUtils; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.*; +import org.openrewrite.staticanalysis.LambdaBlockToExpression; +import java.util.Collections; import java.util.List; /** @@ -60,8 +62,7 @@ public TreeVisitor getVisitor() { public static class ExpectedExceptionToAssertThrowsVisitor extends JavaIsoVisitor { - @Nullable - private JavaParser.Builder javaParser; + private JavaParser.@Nullable Builder javaParser; private JavaParser.Builder javaParser(ExecutionContext ctx) { if (javaParser == null) { @@ -153,15 +154,14 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDecl } } - String exceptionDeclParam = ((isExpectArgAMatcher || isExpectMessageArgAMatcher || isExpectedCauseArgAMatcher) - || expectMessageMethodInvocation != null) ? + String exceptionDeclParam = ((isExpectArgAMatcher || isExpectMessageArgAMatcher || isExpectedCauseArgAMatcher) || + expectMessageMethodInvocation != null) ? "Throwable exception = " : ""; Object expectedExceptionParam = (expectMethodInvocation == null || isExpectArgAMatcher) ? "Exception.class" : expectMethodInvocation.getArguments().get(0); - String templateString = expectedExceptionParam instanceof String ? "#{}assertThrows(#{}, () -> #{});" : "#{}assertThrows(#{any()}, () -> #{});"; - + String templateString = expectedExceptionParam instanceof String ? "#{}assertThrows(#{}, () -> #{any()});" : "#{}assertThrows(#{any()}, () -> #{any()});"; m = JavaTemplate.builder(templateString) .contextSensitive() .javaParser(javaParser(ctx)) @@ -175,7 +175,16 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDecl bodyWithoutExpectedExceptionCalls ); - maybeAddImport("org.junit.jupiter.api.Assertions", "assertThrows"); + // Clear out any declared thrown exceptions + List thrown = m.getThrows(); + if (thrown != null && !thrown.isEmpty()) { + assert m.getBody() != null; + m = m.withBody(m.getBody().withPrefix(thrown.get(0).getPrefix())).withThrows(Collections.emptyList()); + } + + // Unconditionally add the import for assertThrows, got a report where the above template adds the method successfully + // but with missing type attribution for assertThrows so the import was missing + maybeAddImport("org.junit.jupiter.api.Assertions", "assertThrows", false); if (expectMessageMethodInvocation != null && !isExpectMessageArgAMatcher && m.getBody() != null) { m = JavaTemplate.builder("assertTrue(exception.getMessage().contains(#{any(java.lang.String)}));") @@ -218,6 +227,8 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration methodDecl maybeAddImport("org.hamcrest.MatcherAssert", "assertThat"); } + doAfterVisit(new LambdaBlockToExpression().getVisitor()); + return m; } diff --git a/src/main/java/org/openrewrite/java/testing/junit5/GradleUseJunitJupiter.java b/src/main/java/org/openrewrite/java/testing/junit5/GradleUseJunitJupiter.java index 995408803..8d2b1280e 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/GradleUseJunitJupiter.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/GradleUseJunitJupiter.java @@ -17,6 +17,7 @@ import lombok.EqualsAndHashCode; import lombok.Value; +import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; @@ -25,7 +26,6 @@ import org.openrewrite.groovy.GroovyIsoVisitor; import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.ListUtils; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; @@ -194,9 +194,9 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu } break; case "withType": - if (m.getSelect() == null - || !TypeUtils.isOfClassType(m.getSelect().getType(), "org.gradle.api.tasks.TaskContainer") - || !(m.getArguments().get(0) instanceof J.Identifier && "Test".equals(((J.Identifier) m.getArguments().get(0)).getSimpleName()))) { + if (m.getSelect() == null || + !TypeUtils.isOfClassType(m.getSelect().getType(), "org.gradle.api.tasks.TaskContainer") || + !(m.getArguments().get(0) instanceof J.Identifier && "Test".equals(((J.Identifier) m.getArguments().get(0)).getSimpleName()))) { return m; } break; @@ -208,10 +208,10 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu return m; } J.MethodInvocation select = (J.MethodInvocation) m.getSelect(); - if(!"withType".equals(select.getSimpleName()) - || select.getArguments().size() != 1 - || !(select.getArguments().get(0) instanceof J.Identifier) - || !"Test".equals(((J.Identifier) select.getArguments().get(0)).getSimpleName())) { + if(!"withType".equals(select.getSimpleName()) || + select.getArguments().size() != 1 || + !(select.getArguments().get(0) instanceof J.Identifier) || + !"Test".equals(((J.Identifier) select.getArguments().get(0)).getSimpleName())) { return m; } break; diff --git a/src/main/java/org/openrewrite/java/testing/junit5/JUnitParamsRunnerToParameterized.java b/src/main/java/org/openrewrite/java/testing/junit5/JUnitParamsRunnerToParameterized.java index f4bb84386..616ee48c0 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/JUnitParamsRunnerToParameterized.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/JUnitParamsRunnerToParameterized.java @@ -15,9 +15,9 @@ */ package org.openrewrite.java.testing.junit5; +import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.internal.ListUtils; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.*; import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.Expression; @@ -137,8 +137,7 @@ public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ct return anno; } - @Nullable - private String getLiteralAnnotationArgumentValue(J.Annotation anno) { + private @Nullable String getLiteralAnnotationArgumentValue(J.Annotation anno) { String annotationArgumentValue = null; if (anno.getArguments() != null && anno.getArguments().size() == 1 && anno.getArguments().get(0) instanceof J.Literal) { J.Literal literal = (J.Literal) anno.getArguments().get(0); @@ -147,13 +146,12 @@ private String getLiteralAnnotationArgumentValue(J.Annotation anno) { return annotationArgumentValue; } - @Nullable - private String getAnnotationArgumentForInitMethod(J.Annotation anno, String... variableNames) { + private @Nullable String getAnnotationArgumentForInitMethod(J.Annotation anno, String... variableNames) { String value = null; - if (anno.getArguments() != null && anno.getArguments().size() == 1 - && anno.getArguments().get(0) instanceof J.Assignment - && ((J.Assignment) anno.getArguments().get(0)).getVariable() instanceof J.Identifier - && ((J.Assignment) anno.getArguments().get(0)).getAssignment() instanceof J.Literal) { + if (anno.getArguments() != null && anno.getArguments().size() == 1 && + anno.getArguments().get(0) instanceof J.Assignment && + ((J.Assignment) anno.getArguments().get(0)).getVariable() instanceof J.Identifier && + ((J.Assignment) anno.getArguments().get(0)).getAssignment() instanceof J.Literal) { J.Assignment annoArg = (J.Assignment) anno.getArguments().get(0); J.Literal assignment = (J.Literal) annoArg.getAssignment(); String identifier = ((J.Identifier) annoArg.getVariable()).getSimpleName(); @@ -178,8 +176,7 @@ private String getAnnotationArgumentForInitMethod(J.Annotation anno, String... v */ private static class ParametersNoArgsImplicitMethodSource extends JavaIsoVisitor { - @Nullable - private JavaParser.Builder javaParser; + private JavaParser.@Nullable Builder javaParser; private JavaParser.Builder javaParser(ExecutionContext ctx) { if (javaParser == null) { @@ -292,8 +289,7 @@ private J.Annotation maybeReplaceParametersAnnotation(Cursor anno, String method return anno.getValue(); } - @Nullable - private String getAnnotationArgumentValueForMethodTemplate(J.Annotation anno) { + private @Nullable String getAnnotationArgumentValueForMethodTemplate(J.Annotation anno) { String annotationArgumentValue = null; if (anno.getArguments() != null && anno.getArguments().size() == 1) { Expression annoArg = anno.getArguments().get(0); diff --git a/src/main/java/org/openrewrite/java/testing/junit5/LifecycleNonPrivate.java b/src/main/java/org/openrewrite/java/testing/junit5/LifecycleNonPrivate.java index 273362d04..57a3a47d0 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/LifecycleNonPrivate.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/LifecycleNonPrivate.java @@ -67,8 +67,8 @@ private static class LifecycleNonPrivateVisitor extends JavaIsoVisitor lifeCycleAnnotationMatchers.stream() + if (J.Modifier.hasModifier(md.getModifiers(), Type.Private) && + md.getLeadingAnnotations().stream().anyMatch(ann -> lifeCycleAnnotationMatchers.stream() .anyMatch(matcher -> matcher.matches(ann)))) { return maybeAutoFormat(md, md.withModifiers(ListUtils.map(md.getModifiers(), diff --git a/src/main/java/org/openrewrite/java/testing/junit5/MigrateJUnitTestCase.java b/src/main/java/org/openrewrite/java/testing/junit5/MigrateJUnitTestCase.java index ab3036f0b..1bb528abd 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/MigrateJUnitTestCase.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/MigrateJUnitTestCase.java @@ -15,12 +15,12 @@ */ package org.openrewrite.java.testing.junit5; +import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.internal.ListUtils; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.*; import org.openrewrite.java.search.FindAnnotations; import org.openrewrite.java.search.UsesType; @@ -36,8 +36,10 @@ public class MigrateJUnitTestCase extends Recipe { private static final AnnotationMatcher JUNIT_TEST_ANNOTATION_MATCHER = new AnnotationMatcher("@org.junit.Test"); + private static final AnnotationMatcher JUNIT_AFTER_ANNOTATION_MATCHER = new AnnotationMatcher("@org.junit.*After*"); + private static final AnnotationMatcher JUNIT_BEFORE_ANNOTATION_MATCHER = new AnnotationMatcher("@org.junit.*Before*"); - private static boolean isSupertypeTestCase(@Nullable JavaType.FullyQualified fullyQualified) { + private static boolean isSupertypeTestCase(JavaType.@Nullable FullyQualified fullyQualified) { if (fullyQualified == null || fullyQualified.getSupertype() == null || "java.lang.Object".equals(fullyQualified.getFullyQualifiedName())) { return false; } @@ -81,10 +83,10 @@ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionCon @SuppressWarnings("ConstantConditions") @Override - public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + public J.@Nullable MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); - if ((mi.getSelect() != null && TypeUtils.isOfClassType(mi.getSelect().getType(), "junit.framework.TestCase")) - || (mi.getMethodType() != null && TypeUtils.isOfClassType(mi.getMethodType().getDeclaringType(), "junit.framework.TestCase"))) { + if ((mi.getSelect() != null && TypeUtils.isOfClassType(mi.getSelect().getType(), "junit.framework.TestCase")) || + (mi.getMethodType() != null && TypeUtils.isOfClassType(mi.getMethodType().getDeclaringType(), "junit.framework.TestCase"))) { String name = mi.getSimpleName(); // setUp and tearDown will be invoked via Before and After annotations if ("setUp".equals(name) || "tearDown".equals(name)) { @@ -123,9 +125,9 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex updateCursor(md); if (md.getSimpleName().startsWith("test") && md.getLeadingAnnotations().stream().noneMatch(JUNIT_TEST_ANNOTATION_MATCHER::matches)) { md = updateMethodDeclarationAnnotationAndModifier(md, "@Test", "org.junit.jupiter.api.Test", ctx); - } else if ("setUp".equals(md.getSimpleName())) { + } else if ("setUp".equals(md.getSimpleName()) && md.getLeadingAnnotations().stream().noneMatch(JUNIT_BEFORE_ANNOTATION_MATCHER::matches)) { md = updateMethodDeclarationAnnotationAndModifier(md, "@BeforeEach", "org.junit.jupiter.api.BeforeEach", ctx); - } else if ("tearDown".equals(md.getSimpleName())) { + } else if ("tearDown".equals(md.getSimpleName()) && md.getLeadingAnnotations().stream().noneMatch(JUNIT_AFTER_ANNOTATION_MATCHER::matches)) { md = updateMethodDeclarationAnnotationAndModifier(md, "@AfterEach", "org.junit.jupiter.api.AfterEach", ctx); } return md; diff --git a/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java b/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java index e82911cb0..194d0e056 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/MockitoJUnitToMockitoExtension.java @@ -74,6 +74,7 @@ public TreeVisitor getVisitor() { public static class MockitoRuleToMockitoExtensionVisitor extends JavaIsoVisitor { private static final String MOCKITO_RULE_INVOCATION_KEY = "mockitoRuleInvocation"; private static final String MOCKITO_TEST_RULE_INVOCATION_KEY = "mockitoTestRuleInvocation"; + private static final String STRICTNESS_KEY = "strictness"; private static final String EXTEND_WITH_MOCKITO_EXTENSION = "@org.junit.jupiter.api.extension.ExtendWith(org.mockito.junit.jupiter.MockitoExtension.class)"; private static final String RUN_WITH_MOCKITO_JUNIT_RUNNER = "@org.junit.runner.RunWith(org.mockito.runners.MockitoJUnitRunner.class)"; @@ -100,6 +101,7 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex if (classDecl.getBody().getStatements().size() != cd.getBody().getStatements().size() && (FindAnnotations.find(classDecl.withBody(null), RUN_WITH_MOCKITO_JUNIT_RUNNER).isEmpty() && FindAnnotations.find(classDecl.withBody(null), EXTEND_WITH_MOCKITO_EXTENSION).isEmpty())) { + String strictness = getCursor().pollMessage(STRICTNESS_KEY); cd = JavaTemplate.builder("@ExtendWith(MockitoExtension.class)") .javaParser(JavaParser.fromJavaVersion() @@ -110,6 +112,22 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex maybeAddImport("org.junit.jupiter.api.extension.ExtendWith"); maybeAddImport("org.mockito.junit.jupiter.MockitoExtension"); + + if (strictness == null) { + // As we are in a Rule, and rules where always warn by default, + // we cannot use junit5 Strictness.STRICT_STUBS during migration + strictness = "Strictness.WARN"; + } + if (!strictness.contains("STRICT_STUBS")) { + cd = JavaTemplate.builder("@MockitoSettings(strictness = " + strictness + ")") + .javaParser(JavaParser.fromJavaVersion() + .classpathFromResources(ctx, "junit-jupiter-api-5.9", "mockito-junit-jupiter-3.12")) + .imports("org.mockito.junit.jupiter.MockitoSettings", "org.mockito.quality.Strictness") + .build() + .apply(updateCursor(cd), cd.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName))); + maybeAddImport("org.mockito.junit.jupiter.MockitoSettings", false); + maybeAddImport("org.mockito.quality.Strictness", false); + } } } @@ -119,10 +137,27 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex @Override public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { if (method.getMethodType() != null) { + String key = null; if (TypeUtils.isOfClassType(method.getMethodType().getDeclaringType(), "org.mockito.junit.MockitoRule")) { - getCursor().putMessageOnFirstEnclosing(J.MethodDeclaration.class, MOCKITO_RULE_INVOCATION_KEY, method); + key = MOCKITO_RULE_INVOCATION_KEY; } else if (TypeUtils.isOfClassType(method.getMethodType().getDeclaringType(), "org.mockito.junit.MockitoTestRule")) { - getCursor().putMessageOnFirstEnclosing(J.MethodDeclaration.class, MOCKITO_TEST_RULE_INVOCATION_KEY, method); + key = MOCKITO_TEST_RULE_INVOCATION_KEY; + } + if (key != null) { + getCursor().putMessageOnFirstEnclosing(J.MethodDeclaration.class, key, method); + String strictness = null; + switch (method.getSimpleName()) { + case "strictness": + strictness = method.getArguments().get(0).toString(); + break; + case "silent": + strictness = "Strictness.LENIENT"; + break; + } + if (strictness != null) { + strictness = strictness.startsWith("Strictness.") ? strictness : "Strictness." + strictness; + getCursor().putMessageOnFirstEnclosing(J.ClassDeclaration.class, STRICTNESS_KEY, strictness); + } } } diff --git a/src/main/java/org/openrewrite/java/testing/junit5/ParameterizedRunnerToParameterized.java b/src/main/java/org/openrewrite/java/testing/junit5/ParameterizedRunnerToParameterized.java index fa8d51a3e..64c919aac 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/ParameterizedRunnerToParameterized.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/ParameterizedRunnerToParameterized.java @@ -15,9 +15,9 @@ */ package org.openrewrite.java.testing.junit5; +import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.internal.ListUtils; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.*; import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.*; @@ -280,8 +280,8 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex cd = cd.withBody(cd.getBody().withStatements(ListUtils.map(cd.getBody().getStatements(), statement -> { if (statement instanceof J.VariableDeclarations) { J.VariableDeclarations varDecls = (J.VariableDeclarations) statement; - if (varDecls.getVariables().stream().anyMatch(it -> fieldNames.contains(it.getSimpleName())) - && (varDecls.hasModifier(J.Modifier.Type.Final))) { + if (varDecls.getVariables().stream().anyMatch(it -> fieldNames.contains(it.getSimpleName())) && + (varDecls.hasModifier(J.Modifier.Type.Final))) { varDecls = varDecls.withModifiers(ListUtils.map(varDecls.getModifiers(), mod -> mod.getType() == J.Modifier.Type.Final ? null : mod)); statement = maybeAutoFormat(statement, varDecls, ctx, new Cursor(getCursor(), finalBody)); } diff --git a/src/main/java/org/openrewrite/java/testing/junit5/RemoveTryCatchFailBlocks.java b/src/main/java/org/openrewrite/java/testing/junit5/RemoveTryCatchFailBlocks.java index 7e17762f6..eba2eca36 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/RemoveTryCatchFailBlocks.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/RemoveTryCatchFailBlocks.java @@ -19,16 +19,14 @@ import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; -import org.openrewrite.java.JavaParser; -import org.openrewrite.java.JavaTemplate; -import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.*; import org.openrewrite.java.search.UsesMethod; import org.openrewrite.java.tree.*; import java.util.Collections; import java.util.Objects; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; public class RemoveTryCatchFailBlocks extends Recipe { @@ -67,6 +65,19 @@ public J visitTry(J.Try jtry, ExecutionContext ctx) { return try_; } + // Skip if any return is found, since we can't return from `assertDoesNotThrow` + AtomicBoolean returnFound = new AtomicBoolean(false); + new JavaIsoVisitor() { + @Override + public J.Return visitReturn(J.Return _return, AtomicBoolean atomicBoolean) { + atomicBoolean.set(true); + return _return; + } + }.visit(try_, returnFound, getCursor().getParentOrThrow()); + if (returnFound.get()) { + return try_; + } + /* Only one statement in the catch block, which is a fail(), with no or a simple String argument. We would not want to convert for instance fail(cleanUpAndReturnMessage()) might still have side @@ -81,9 +92,9 @@ We would not want to convert for instance fail(cleanUpAndReturnMessage()) might return try_; } J.MethodInvocation failCall = (J.MethodInvocation) statement; - if (!ASSERT_FAIL_NO_ARG.matches(failCall) - && !ASSERT_FAIL_STRING_ARG.matches(failCall) - && !ASSERT_FAIL_THROWABLE_ARG.matches(failCall)) { + if (!ASSERT_FAIL_NO_ARG.matches(failCall) && + !ASSERT_FAIL_STRING_ARG.matches(failCall) && + !ASSERT_FAIL_THROWABLE_ARG.matches(failCall)) { return try_; } diff --git a/src/main/java/org/openrewrite/java/testing/junit5/RunnerToExtension.java b/src/main/java/org/openrewrite/java/testing/junit5/RunnerToExtension.java index 660f756b6..90f9f71fc 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/RunnerToExtension.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/RunnerToExtension.java @@ -19,8 +19,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.EqualsAndHashCode; import lombok.Value; +import org.jspecify.annotations.Nullable; import org.openrewrite.*; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; diff --git a/src/main/java/org/openrewrite/java/testing/junit5/TempDirNonFinal.java b/src/main/java/org/openrewrite/java/testing/junit5/TempDirNonFinal.java index 6ba7eb1e1..d843cf349 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/TempDirNonFinal.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/TempDirNonFinal.java @@ -50,8 +50,8 @@ private static class TempDirVisitor extends JavaIsoVisitor { @Override public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { J.VariableDeclarations varDecls = super.visitVariableDeclarations(multiVariable, ctx); - if (varDecls.getLeadingAnnotations().stream().anyMatch(TEMPDIR_ANNOTATION_MATCHER::matches) - && varDecls.hasModifier(Type.Final) && isField(getCursor())) { + if (varDecls.getLeadingAnnotations().stream().anyMatch(TEMPDIR_ANNOTATION_MATCHER::matches) && + varDecls.hasModifier(Type.Final) && isField(getCursor())) { return maybeAutoFormat(varDecls, varDecls.withModifiers(ListUtils .map(varDecls.getModifiers(), modifier -> modifier.getType() == Type.Final ? null : modifier)), ctx, getCursor().getParentOrThrow()); diff --git a/src/main/java/org/openrewrite/java/testing/junit5/TemporaryFolderToTempDir.java b/src/main/java/org/openrewrite/java/testing/junit5/TemporaryFolderToTempDir.java index 7137f3ada..d6b3bf130 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/TemporaryFolderToTempDir.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/TemporaryFolderToTempDir.java @@ -15,11 +15,11 @@ */ package org.openrewrite.java.testing.junit5; +import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.*; import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.*; @@ -49,8 +49,8 @@ public TreeVisitor getVisitor() { final AnnotationMatcher classRule = new AnnotationMatcher("@org.junit.ClassRule"); final AnnotationMatcher rule = new AnnotationMatcher("@org.junit.Rule"); - @Nullable - private JavaParser.Builder javaParser; + + private JavaParser.@Nullable Builder javaParser; private JavaParser.Builder javaParser(ExecutionContext ctx) { if (javaParser == null) { @@ -103,16 +103,16 @@ public J visitVariableDeclarations(J.VariableDeclarations multiVariable, Executi } private boolean isRuleAnnotatedTemporaryFolder(J.VariableDeclarations vd) { - return TypeUtils.isOfClassType(vd.getTypeAsFullyQualified(), "org.junit.rules.TemporaryFolder") - && vd.getLeadingAnnotations().stream().anyMatch(anno -> classRule.matches(anno) || rule.matches(anno)); + return TypeUtils.isOfClassType(vd.getTypeAsFullyQualified(), "org.junit.rules.TemporaryFolder") && + vd.getLeadingAnnotations().stream().anyMatch(anno -> classRule.matches(anno) || rule.matches(anno)); } @Override - public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + public @Nullable J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { J.MethodInvocation mi = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); updateCursor(mi); - if (mi.getSelect() != null && mi.getMethodType() != null - && TypeUtils.isOfClassType(mi.getMethodType().getDeclaringType(), "org.junit.rules.TemporaryFolder")) { + if (mi.getSelect() != null && mi.getMethodType() != null && + TypeUtils.isOfClassType(mi.getMethodType().getDeclaringType(), "org.junit.rules.TemporaryFolder")) { switch (mi.getSimpleName()) { case "newFile": return convertToNewFile(mi, ctx); @@ -157,8 +157,8 @@ private J convertToNewFile(J.MethodInvocation mi, ExecutionContext ctx) { private static class AddNewFolderMethod extends JavaIsoVisitor { private final J.MethodInvocation methodInvocation; - @Nullable - private JavaParser.Builder javaParser; + + private JavaParser.@Nullable Builder javaParser; private JavaParser.Builder javaParser(ExecutionContext ctx) { if (javaParser == null) { @@ -236,8 +236,8 @@ private static class TranslateNewFolderMethodInvocation extends JavaVisitor javaParser; + + private JavaParser.@Nullable Builder javaParser; private JavaParser.Builder javaParser(ExecutionContext ctx) { if (javaParser == null) { diff --git a/src/main/java/org/openrewrite/java/testing/junit5/TestRuleToTestInfo.java b/src/main/java/org/openrewrite/java/testing/junit5/TestRuleToTestInfo.java index 28cef3cad..5d97b74dc 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/TestRuleToTestInfo.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/TestRuleToTestInfo.java @@ -15,12 +15,12 @@ */ package org.openrewrite.java.testing.junit5; +import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.internal.ListUtils; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.*; import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.J; @@ -54,8 +54,7 @@ private static class TestRuleToTestInfoVisitor extends JavaIsoVisitor javaParser; + private JavaParser.@Nullable Builder javaParser; private JavaParser.Builder javaParser(ExecutionContext ctx) { if (javaParser == null) { @@ -102,7 +101,7 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m } @Override - public J.NewClass visitNewClass(J.NewClass newClass, ExecutionContext ctx) { + public J.@Nullable NewClass visitNewClass(J.NewClass newClass, ExecutionContext ctx) { J.NewClass nc = super.visitNewClass(newClass, ctx); if (TypeUtils.isOfClassType(nc.getType(), "org.junit.rules.TestName")) { //noinspection ConstantConditions @@ -162,8 +161,7 @@ private static class BeforeMethodToTestInfoVisitor extends JavaIsoVisitor javaParser; + private JavaParser.@Nullable Builder javaParser; private JavaParser.Builder javaParser(ExecutionContext ctx) { if (javaParser == null) { diff --git a/src/main/java/org/openrewrite/java/testing/junit5/UpdateBeforeAfterAnnotations.java b/src/main/java/org/openrewrite/java/testing/junit5/UpdateBeforeAfterAnnotations.java index 832634e32..1809e14ab 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/UpdateBeforeAfterAnnotations.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/UpdateBeforeAfterAnnotations.java @@ -49,15 +49,13 @@ public TreeVisitor getVisitor() { public static class UpdateBeforeAfterAnnotationsVisitor extends JavaIsoVisitor { @Override - public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { - //This visitor handles changing the method visibility for any method annotated with one of the four before/after - //annotations. It registers visitors that will sweep behind it making the type changes. + public J preVisit(J tree, ExecutionContext ctx) { + stopAfterPreVisit(); doAfterVisit(new ChangeType("org.junit.Before", "org.junit.jupiter.api.BeforeEach", true).getVisitor()); doAfterVisit(new ChangeType("org.junit.After", "org.junit.jupiter.api.AfterEach", true).getVisitor()); doAfterVisit(new ChangeType("org.junit.BeforeClass", "org.junit.jupiter.api.BeforeAll", true).getVisitor()); doAfterVisit(new ChangeType("org.junit.AfterClass", "org.junit.jupiter.api.AfterAll", true).getVisitor()); - - return super.visitCompilationUnit(cu, ctx); + return tree; } } } diff --git a/src/main/java/org/openrewrite/java/testing/junit5/UpdateMockWebServer.java b/src/main/java/org/openrewrite/java/testing/junit5/UpdateMockWebServer.java index 05eb54297..3c6389e1b 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/UpdateMockWebServer.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/UpdateMockWebServer.java @@ -15,9 +15,9 @@ */ package org.openrewrite.java.testing.junit5; +import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.internal.ListUtils; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.AnnotationMatcher; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaParser; @@ -70,8 +70,7 @@ public TreeVisitor getVisitor() { new UsesType<>("okhttp3.mockwebserver.MockWebServer", false) ), new JavaIsoVisitor() { - @Nullable - private JavaParser.Builder javaParser; + private JavaParser.@Nullable Builder javaParser; private JavaParser.Builder javaParser(ExecutionContext ctx) { if (javaParser == null) { diff --git a/src/main/java/org/openrewrite/java/testing/junit5/UpdateTestAnnotation.java b/src/main/java/org/openrewrite/java/testing/junit5/UpdateTestAnnotation.java index dd603a9bc..ddae37181 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/UpdateTestAnnotation.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/UpdateTestAnnotation.java @@ -15,18 +15,20 @@ */ package org.openrewrite.java.testing.junit5; +import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; import org.openrewrite.internal.ListUtils; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.*; import org.openrewrite.java.search.FindImports; import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markup; +import org.openrewrite.staticanalysis.LambdaBlockToExpression; +import java.util.Collections; import java.util.Comparator; import java.util.Set; @@ -53,8 +55,7 @@ public TreeVisitor getVisitor() { private static class UpdateTestAnnotationVisitor extends JavaIsoVisitor { private static final AnnotationMatcher JUNIT4_TEST = new AnnotationMatcher("@org.junit.Test"); - @Nullable - private JavaParser.Builder javaParser; + private JavaParser.@Nullable Builder javaParser; private JavaParser.Builder javaParser(ExecutionContext ctx) { if (javaParser == null) { @@ -64,7 +65,6 @@ private static class UpdateTestAnnotationVisitor extends JavaIsoVisitor javaParser; + private JavaParser.@Nullable Builder javaParser; private JavaParser.Builder javaParser(ExecutionContext ctx) { if (javaParser == null) { diff --git a/src/main/java/org/openrewrite/java/testing/junit5/UseAssertSame.java b/src/main/java/org/openrewrite/java/testing/junit5/UseAssertSame.java new file mode 100644 index 000000000..1dc6f250f --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/junit5/UseAssertSame.java @@ -0,0 +1,106 @@ +/* + * Copyright 2024 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.junit5; + +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.MethodMatcher; +import org.openrewrite.java.search.UsesMethod; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + +import java.util.ArrayList; +import java.util.List; + +public class UseAssertSame extends Recipe { + @Override + public String getDisplayName() { + return "Use JUnit5's `assertSame` or `assertNotSame` instead of `assertTrue(... == ...)`"; + } + + @Override + public String getDescription() { + return "Prefers the usage of `assertSame` or `assertNotSame` methods instead of using of vanilla `assertTrue` " + + "or `assertFalse` with a boolean comparison."; + } + + private static final MethodMatcher ASSERT_TRUE_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertTrue(..)"); + private static final MethodMatcher ASSERT_FALSE_MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertFalse(..)"); + + @Override + public TreeVisitor getVisitor() { + JavaIsoVisitor visitor = new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation methodInvocation, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(methodInvocation, ctx); + if (!ASSERT_TRUE_MATCHER.matches(mi) && !ASSERT_FALSE_MATCHER.matches(mi)) { + return mi; + } else if (mi.getMethodType() == null) { + return mi; + } + + Expression firstArgument = mi.getArguments().get(0); + if (!(firstArgument instanceof J.Binary)) { + return mi; + } + J.Binary binary = (J.Binary) firstArgument; + if (binary.getOperator() != J.Binary.Type.Equal && binary.getOperator() != J.Binary.Type.NotEqual) { + return mi; + } + List newArguments = new ArrayList<>(); + newArguments.add(binary.getLeft()); + newArguments.add(binary.getRight()); + newArguments.addAll(mi.getArguments().subList(1, mi.getArguments().size())); + + String newMethodName = binary.getOperator() == J.Binary.Type.Equal == ASSERT_TRUE_MATCHER.matches(mi) ? + "assertSame" : "assertNotSame"; + + maybeRemoveImport("org.junit.jupiter.api.Assertions"); + maybeAddImport("org.junit.jupiter.api.Assertions", newMethodName); + + JavaType.Method newType = assertSameMethodType(mi, newMethodName); + return mi.withName(mi.getName().withSimpleName(newMethodName).withType(newType)) + .withMethodType(newType) + .withArguments(newArguments); + } + + private JavaType.Method assertSameMethodType(J.MethodInvocation mi, String newMethodName) { + JavaType.Method assertTrue = mi.getMethodType(); + assert assertTrue != null; + int parameterCount = assertTrue.getParameterTypes().size(); + JavaType.FullyQualified assertions = assertTrue.getDeclaringType(); + for (JavaType.Method method : assertions.getMethods()) { + if (method.getName().equals("assertSame") && method.getParameterNames().size() == parameterCount + 1 && + assertTrue.getParameterTypes().get(parameterCount - 1).equals(method.getParameterTypes().get(parameterCount))) { + return method; + } + } + // fallback when type attribution was stubbed + return assertTrue.withName(newMethodName); + } + }; + return Preconditions.check( + Preconditions.or( + new UsesMethod<>(ASSERT_TRUE_MATCHER), + new UsesMethod<>(ASSERT_FALSE_MATCHER)), + visitor); + } + +} diff --git a/src/main/java/org/openrewrite/java/testing/junit5/UseTestMethodOrder.java b/src/main/java/org/openrewrite/java/testing/junit5/UseTestMethodOrder.java index 41e4f8127..4525701a8 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/UseTestMethodOrder.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/UseTestMethodOrder.java @@ -15,11 +15,11 @@ */ package org.openrewrite.java.testing.junit5; +import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaTemplate; @@ -45,8 +45,8 @@ public String getDescription() { public TreeVisitor getVisitor() { return Preconditions.check(new UsesType<>("org.junit.FixMethodOrder", false), new JavaIsoVisitor() { - @Nullable - private JavaParser.Builder javaParser; + + private JavaParser.@Nullable Builder javaParser; private JavaParser.Builder javaParser(ExecutionContext ctx) { if (javaParser == null) { diff --git a/src/main/java/org/openrewrite/java/testing/junit5/package-info.java b/src/main/java/org/openrewrite/java/testing/junit5/package-info.java index db2dbd7ea..b05bb236e 100644 --- a/src/main/java/org/openrewrite/java/testing/junit5/package-info.java +++ b/src/main/java/org/openrewrite/java/testing/junit5/package-info.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@NonNullApi +@NullMarked @NonNullFields package org.openrewrite.java.testing.junit5; -import org.openrewrite.internal.lang.NonNullApi; +import org.jspecify.annotations.NullMarked; import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/main/java/org/openrewrite/java/testing/mockito/AnyToNullable.java b/src/main/java/org/openrewrite/java/testing/mockito/AnyToNullable.java index d9a201b17..8595085d2 100644 --- a/src/main/java/org/openrewrite/java/testing/mockito/AnyToNullable.java +++ b/src/main/java/org/openrewrite/java/testing/mockito/AnyToNullable.java @@ -15,8 +15,8 @@ */ package org.openrewrite.java.testing.mockito; +import org.jspecify.annotations.Nullable; import org.openrewrite.*; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.ChangeMethodName; import org.openrewrite.java.ChangeMethodTargetToStatic; import org.openrewrite.java.tree.J; diff --git a/src/main/java/org/openrewrite/java/testing/mockito/CleanupMockitoImports.java b/src/main/java/org/openrewrite/java/testing/mockito/CleanupMockitoImports.java index 8f9fcae2e..08ada5309 100644 --- a/src/main/java/org/openrewrite/java/testing/mockito/CleanupMockitoImports.java +++ b/src/main/java/org/openrewrite/java/testing/mockito/CleanupMockitoImports.java @@ -15,11 +15,11 @@ */ package org.openrewrite.java.testing.mockito; +import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.Preconditions; import org.openrewrite.Recipe; import org.openrewrite.TreeVisitor; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.J; @@ -47,7 +47,9 @@ public String getDescription() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new UsesType<>("org.mockito.*", false), new CleanupMockitoImportsVisitor()); + return Preconditions.check( + new UsesType<>("org.mockito.*", false), + new CleanupMockitoImportsVisitor()); } private static class CleanupMockitoImportsVisitor extends JavaIsoVisitor { @@ -104,12 +106,18 @@ private static class CleanupMockitoImportsVisitor extends JavaIsoVisitor qualifiedMethods) { J.MethodInvocation mi = super.visitMethodInvocation(method, qualifiedMethods); - if (MOCKITO_METHOD_NAMES.contains(mi.getSimpleName()) - && mi.getSelect() != null - && TypeUtils.isAssignableTo("org.mockito.Mockito", mi.getSelect().getType())) { + if (MOCKITO_METHOD_NAMES.contains(mi.getSimpleName()) && + mi.getSelect() != null && + TypeUtils.isAssignableTo("org.mockito.Mockito", mi.getSelect().getType())) { qualifiedMethods.add(mi.getSimpleName()); } return mi; diff --git a/src/main/java/org/openrewrite/java/testing/mockito/MockitoUtils.java b/src/main/java/org/openrewrite/java/testing/mockito/MockitoUtils.java new file mode 100644 index 000000000..ad143e008 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/mockito/MockitoUtils.java @@ -0,0 +1,93 @@ +/* + * Copyright 2024 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.jspecify.annotations.Nullable; +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.Statement; + +import java.util.List; +import java.util.stream.Collectors; + +public class MockitoUtils { + public static J.ClassDeclaration maybeAddMethodWithAnnotation( + JavaVisitor visitor, + J.ClassDeclaration classDecl, + ExecutionContext ctx, + boolean isPublic, + String methodName, + String methodAnnotationSignature, + String methodAnnotationToAdd, + String additionalClasspathResource, + String importToAdd, + String methodAnnotationParameters + ) { + if (hasMethodWithAnnotation(classDecl, new AnnotationMatcher(methodAnnotationSignature))) { + return classDecl; + } + + J.MethodDeclaration firstTestMethod = getFirstTestMethod( + classDecl.getBody().getStatements().stream().filter(J.MethodDeclaration.class::isInstance) + .map(J.MethodDeclaration.class::cast).collect(Collectors.toList())); + + visitor.maybeAddImport(importToAdd); + String tplStr = methodAnnotationToAdd + methodAnnotationParameters + + (isPublic ? " public" : "") + " void " + methodName + "() {}"; + return JavaTemplate.builder(tplStr) + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, additionalClasspathResource)) + .imports(importToAdd) + .build() + .apply( + new Cursor(visitor.getCursor().getParentOrThrow(), classDecl), + (firstTestMethod != null) ? + firstTestMethod.getCoordinates().before() : + classDecl.getBody().getCoordinates().lastStatement() + ); + } + + private static boolean hasMethodWithAnnotation(J.ClassDeclaration classDecl, AnnotationMatcher annotationMatcher) { + for (Statement statement : classDecl.getBody().getStatements()) { + if (statement instanceof J.MethodDeclaration) { + J.MethodDeclaration methodDeclaration = (J.MethodDeclaration) statement; + List allAnnotations = methodDeclaration.getAllAnnotations(); + for (J.Annotation annotation : allAnnotations) { + if (annotationMatcher.matches(annotation)) { + return true; + } + } + } + } + return false; + } + + private static J.@Nullable MethodDeclaration getFirstTestMethod(List methods) { + for (J.MethodDeclaration methodDeclaration : methods) { + for (J.Annotation annotation : methodDeclaration.getLeadingAnnotations()) { + if ("Test".equals(annotation.getSimpleName())) { + return methodDeclaration; + } + } + } + return null; + } +} diff --git a/src/main/java/org/openrewrite/java/testing/mockito/MockitoWhenOnStaticToMockStatic.java b/src/main/java/org/openrewrite/java/testing/mockito/MockitoWhenOnStaticToMockStatic.java new file mode 100644 index 000000000..52c9c9200 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/mockito/MockitoWhenOnStaticToMockStatic.java @@ -0,0 +1,136 @@ +/* + * Copyright 2024 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.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.VariableNameUtils; +import org.openrewrite.java.search.UsesMethod; +import org.openrewrite.java.tree.Flag; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.Statement; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +public class MockitoWhenOnStaticToMockStatic extends Recipe { + + private static final MethodMatcher MOCKITO_WHEN = new MethodMatcher("org.mockito.Mockito when(..)"); + + @Override + public String getDisplayName() { + return "Replace `Mockito.when` on static (non mock) with try-with-resource with MockedStatic"; + } + + @Override + public String getDescription() { + return "Replace `Mockito.when` on static (non mock) with try-with-resource with MockedStatic as Mockito4 no longer allows this."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesMethod<>(MOCKITO_WHEN), new JavaIsoVisitor() { + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + J.MethodDeclaration m = super.visitMethodDeclaration(method, ctx); + if (m.getBody() == null) { + return m; + } + + List newStatements = maybeWrapStatementsInTryWithResourcesMockedStatic(m, m.getBody().getStatements()); + return maybeAutoFormat(m, m.withBody(m.getBody().withStatements(newStatements)), ctx); + } + + private List maybeWrapStatementsInTryWithResourcesMockedStatic(J.MethodDeclaration m, List remainingStatements) { + AtomicBoolean restInTry = new AtomicBoolean(false); + return ListUtils.flatMap(remainingStatements, (index, statement) -> { + if (restInTry.get()) { + // Rest of the statements have ended up in the try block + return Collections.emptyList(); + } + + if (statement instanceof J.MethodInvocation && + MOCKITO_WHEN.matches(((J.MethodInvocation) statement).getSelect())) { + J.MethodInvocation when = (J.MethodInvocation) ((J.MethodInvocation) statement).getSelect(); + if (when != null && when.getArguments().get(0) instanceof J.MethodInvocation) { + J.MethodInvocation whenArg = (J.MethodInvocation) when.getArguments().get(0); + if (whenArg.getMethodType() != null && whenArg.getMethodType().hasFlags(Flag.Static)) { + if (whenArg.getSelect() instanceof J.Identifier) { + J.Identifier clazz = (J.Identifier) whenArg.getSelect(); + if (clazz.getType() != null) { + return tryWithMockedStatic(m, remainingStatements, index, statement, clazz.getSimpleName(), whenArg, restInTry); + } + } else if (whenArg.getSelect() instanceof J.FieldAccess) { + J.FieldAccess fieldAccess = (J.FieldAccess) whenArg.getSelect(); + if (fieldAccess.getTarget() instanceof J.Identifier) { + J.Identifier clazz = (J.Identifier) fieldAccess.getTarget(); + if (clazz.getType() != null) { + return tryWithMockedStatic(m, remainingStatements, index, statement, clazz.getSimpleName(), whenArg, restInTry); + } + } + } + } + } + } + return statement; + }); + } + + private J.Try tryWithMockedStatic( + J.MethodDeclaration m, + List remainingStatements, + Integer index, + Statement statement, + String simpleName, + J.MethodInvocation whenArg, + AtomicBoolean restInTry) { + String mockName = VariableNameUtils.generateVariableName("mock" + simpleName, updateCursor(m), VariableNameUtils.GenerationStrategy.INCREMENT_NUMBER); + maybeAddImport("org.mockito.MockedStatic", false); + maybeAddImport("org.mockito.Mockito", "mockStatic"); + String template = String.format( + "try(MockedStatic<%1$s> %2$s = mockStatic(%1$s.class)) {\n" + + " %2$s.when(#{any()}).thenReturn(#{any()});\n" + + "}", simpleName, mockName); + J.Try try_ = (J.Try) ((J.MethodDeclaration) JavaTemplate.builder(template) + .contextSensitive() + .imports("org.mockito.MockedStatic") + .staticImports("org.mockito.Mockito.mockStatic") + .build() + .apply(getCursor(), m.getCoordinates().replaceBody(), + whenArg, ((J.MethodInvocation) statement).getArguments().get(0))) + .getBody().getStatements().get(0); + + restInTry.set(true); + + List precedingStatements = remainingStatements.subList(0, index); + return try_.withBody(try_.getBody().withStatements(ListUtils.concatAll( + try_.getBody().getStatements(), + maybeWrapStatementsInTryWithResourcesMockedStatic( + m.withBody(m.getBody().withStatements(ListUtils.concat(precedingStatements, try_))), + remainingStatements.subList(index + 1, remainingStatements.size()) + )))) + .withPrefix(statement.getPrefix()); + } + }); + } +} diff --git a/src/main/java/org/openrewrite/java/testing/mockito/PowerMockitoMockStaticToMockito.java b/src/main/java/org/openrewrite/java/testing/mockito/PowerMockitoMockStaticToMockito.java index a0450c661..dbc259a8e 100644 --- a/src/main/java/org/openrewrite/java/testing/mockito/PowerMockitoMockStaticToMockito.java +++ b/src/main/java/org/openrewrite/java/testing/mockito/PowerMockitoMockStaticToMockito.java @@ -15,9 +15,9 @@ */ package org.openrewrite.java.testing.mockito; +import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.internal.ListUtils; -import org.openrewrite.internal.lang.Nullable; import org.openrewrite.java.*; import org.openrewrite.java.search.FindAnnotations; import org.openrewrite.java.search.UsesType; @@ -26,6 +26,8 @@ import java.util.*; import java.util.stream.Collectors; +import static org.openrewrite.java.testing.mockito.MockitoUtils.maybeAddMethodWithAnnotation; + public class PowerMockitoMockStaticToMockito extends Recipe { @Override @@ -43,8 +45,8 @@ public String getDescription() { public TreeVisitor getVisitor() { return Preconditions.check( Preconditions.or( - new UsesType<>("org.powermock..*", false), - new UsesType<>("org.mockito..*", false) + new UsesType<>("org.powermock..*", false), + new UsesType<>("org.mockito..*", false) ), new PowerMockitoToMockitoVisitor() ); @@ -69,6 +71,7 @@ private static class PowerMockitoToMockitoVisitor extends JavaVisitor mockStaticInvocationsByClassName = getCursor().getNearestMessage(MOCK_STATIC_INVOCATIONS); @@ -189,7 +192,9 @@ public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) if (MOCKED_STATIC_MATCHER.matches(mi)) { determineTestGroups(); - if (!getCursor().getPath(o -> o instanceof J.Assignment || o instanceof J.Try.Resource).hasNext()) { + if (!getCursor().getPath(o -> o instanceof J.VariableDeclarations || + o instanceof J.Assignment || + o instanceof J.Try.Resource).hasNext()) { //noinspection DataFlowIssue return null; } @@ -210,33 +215,6 @@ private static boolean isFieldAlreadyDefined(J.Block classBody, String fieldName return false; } - @Nullable - private static J.MethodDeclaration getFirstTestMethod(List methods) { - for (J.MethodDeclaration methodDeclaration : methods) { - for (J.Annotation annotation : methodDeclaration.getLeadingAnnotations()) { - if ("Test".equals(annotation.getSimpleName())) { - return methodDeclaration; - } - } - } - return null; - } - - private static boolean hasMethodWithAnnotation(J.ClassDeclaration classDecl, AnnotationMatcher annotationMatcher) { - for (Statement statement : classDecl.getBody().getStatements()) { - if (statement instanceof J.MethodDeclaration) { - J.MethodDeclaration methodDeclaration = (J.MethodDeclaration) statement; - List allAnnotations = methodDeclaration.getAllAnnotations(); - for (J.Annotation annotation : allAnnotations) { - if (annotationMatcher.matches(annotation)) { - return true; - } - } - } - } - return false; - } - private static boolean isStaticMockAlreadyClosed(J.Identifier staticMock, J.Block methodBody) { for (Statement statement : methodBody.getStatements()) { if (statement instanceof J.MethodInvocation) { @@ -446,7 +424,7 @@ private J.ClassDeclaration addFieldDeclarationForMockedTypes(J.ClassDeclaration new Cursor(getCursor().getParentOrThrow(), classDecl), classDecl.getBody().getCoordinates().firstStatement(), classlessTypeName, - classlessTypeName + classlessTypeName.replace(".", "_") ); J.VariableDeclarations mockField = (J.VariableDeclarations) classDecl.getBody().getStatements().get(0); @@ -463,7 +441,7 @@ private J.ClassDeclaration addFieldDeclarationForMockedTypes(J.ClassDeclaration private J.ClassDeclaration maybeAddSetUpMethodBody(J.ClassDeclaration classDecl, ExecutionContext ctx) { String testGroupsAsString = getTestGroupsAsString(); - return maybeAddMethodWithAnnotation(classDecl, ctx, "setUpStaticMocks", + return maybeAddMethodWithAnnotation(this, classDecl, ctx, false, "setUpStaticMocks", setUpMethodAnnotationSignature, setUpMethodAnnotation, additionalClasspathResource, setUpImportToAdd, testGroupsAsString); } @@ -481,38 +459,12 @@ private String getTestGroupsAsString() { private J.ClassDeclaration maybeAddTearDownMethodBody(J.ClassDeclaration classDecl, ExecutionContext ctx) { String testGroupsAsString = (getTestGroupsAsString().isEmpty()) ? tearDownMethodAnnotationParameters : getTestGroupsAsString(); - return maybeAddMethodWithAnnotation(classDecl, ctx, "tearDownStaticMocks", + return maybeAddMethodWithAnnotation(this, classDecl, ctx, false, "tearDownStaticMocks", tearDownMethodAnnotationSignature, tearDownMethodAnnotation, additionalClasspathResource, tearDownImportToAdd, testGroupsAsString); } - private J.ClassDeclaration maybeAddMethodWithAnnotation(J.ClassDeclaration classDecl, ExecutionContext ctx, - String methodName, String methodAnnotationSignature, - String methodAnnotationToAdd, - String additionalClasspathResource, String importToAdd, - String methodAnnotationParameters) { - if (hasMethodWithAnnotation(classDecl, new AnnotationMatcher(methodAnnotationSignature))) { - return classDecl; - } - - J.MethodDeclaration firstTestMethod = getFirstTestMethod( - classDecl.getBody().getStatements().stream().filter(J.MethodDeclaration.class::isInstance) - .map(J.MethodDeclaration.class::cast).collect(Collectors.toList())); - - maybeAddImport(importToAdd); - return JavaTemplate.builder(methodAnnotationToAdd + methodAnnotationParameters + " void " + methodName + "() {}") - .contextSensitive() - .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, additionalClasspathResource)) - .imports(importToAdd) - .build() - .apply( - new Cursor(getCursor().getParentOrThrow(), classDecl), - (firstTestMethod != null) ? - firstTestMethod.getCoordinates().before() : - classDecl.getBody().getCoordinates().lastStatement() - ); - } private J.MethodInvocation modifyWhenMethodInvocation(J.MethodInvocation whenMethod) { List methodArguments = whenMethod.getArguments(); @@ -559,8 +511,7 @@ private J.MethodInvocation modifyWhenMethodInvocation(J.MethodInvocation whenMet return whenMethod; } - @Nullable - private String getDeclaringClassName(J.MethodInvocation mi) { + private @Nullable String getDeclaringClassName(J.MethodInvocation mi) { JavaType.Method methodType = mi.getMethodType(); if (methodType != null) { JavaType.FullyQualified declaringType = methodType.getDeclaringType(); @@ -569,8 +520,7 @@ private String getDeclaringClassName(J.MethodInvocation mi) { return null; } - @Nullable - private J.Identifier getFieldIdentifier(String fieldName) { + private J.@Nullable Identifier getFieldIdentifier(String fieldName) { return getMockedTypesFields().keySet().stream() .filter(identifier -> identifier.getSimpleName().equals(fieldName)).findFirst() .orElseGet(() -> { diff --git a/src/main/java/org/openrewrite/java/testing/mockito/SimplifyMockitoVerifyWhenGiven.java b/src/main/java/org/openrewrite/java/testing/mockito/SimplifyMockitoVerifyWhenGiven.java new file mode 100644 index 000000000..e837aa9be --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/mockito/SimplifyMockitoVerifyWhenGiven.java @@ -0,0 +1,92 @@ +/* + * Copyright 2024 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.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.JavaIsoVisitor; +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.MethodCall; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class SimplifyMockitoVerifyWhenGiven extends Recipe { + + private static final MethodMatcher WHEN_MATCHER = new MethodMatcher("org.mockito.Mockito when(..)"); + private static final MethodMatcher GIVEN_MATCHER = new MethodMatcher("org.mockito.BDDMockito given(..)"); + private static final MethodMatcher VERIFY_MATCHER = new MethodMatcher("org.mockito.Mockito verify(..)"); + private static final MethodMatcher STUBBER_MATCHER = new MethodMatcher("org.mockito.stubbing.Stubber when(..)"); + private static final MethodMatcher EQ_MATCHER = new MethodMatcher("org.mockito.ArgumentMatchers eq(..)"); + private static final MethodMatcher MOCKITO_EQ_MATCHER = new MethodMatcher("org.mockito.Mockito eq(..)"); + + @Override + public String getDisplayName() { + return "Call to Mockito method \"verify\", \"when\" or \"given\" should be simplified"; + } + + @Override + public String getDescription() { + return "Fixes Sonar issue `java:S6068`: Call to Mockito method \"verify\", \"when\" or \"given\" should be simplified."; + } + + @Override + public Set getTags() { + return Collections.singleton("RSPEC-6068"); + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(Preconditions.or(new UsesMethod<>(EQ_MATCHER), new UsesMethod<>(MOCKITO_EQ_MATCHER)), + new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation methodInvocation, ExecutionContext ctx) { + J.MethodInvocation mi = super.visitMethodInvocation(methodInvocation, ctx); + + if ((WHEN_MATCHER.matches(mi) || GIVEN_MATCHER.matches(mi)) && mi.getArguments().get(0) instanceof J.MethodInvocation) { + List updatedArguments = new ArrayList<>(mi.getArguments()); + updatedArguments.set(0, checkAndUpdateEq((J.MethodInvocation) mi.getArguments().get(0))); + mi = mi.withArguments(updatedArguments); + } else if (VERIFY_MATCHER.matches(mi.getSelect()) || + STUBBER_MATCHER.matches(mi.getSelect())) { + mi = checkAndUpdateEq(mi); + } + + maybeRemoveImport("org.mockito.ArgumentMatchers.eq"); + maybeRemoveImport("org.mockito.Mockito.eq"); + return mi; + } + + private J.MethodInvocation checkAndUpdateEq(J.MethodInvocation methodInvocation) { + if (methodInvocation.getArguments().stream().allMatch(arg -> EQ_MATCHER.matches(arg) || + MOCKITO_EQ_MATCHER.matches(arg))) { + return methodInvocation.withArguments(ListUtils.map(methodInvocation.getArguments(), invocation -> + ((MethodCall) invocation).getArguments().get(0).withPrefix(invocation.getPrefix()))); + } + return methodInvocation; + } + }); + } + +} diff --git a/src/main/java/org/openrewrite/java/testing/mockito/VerifyZeroToNoMoreInteractions.java b/src/main/java/org/openrewrite/java/testing/mockito/VerifyZeroToNoMoreInteractions.java new file mode 100644 index 000000000..2315e88ba --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/mockito/VerifyZeroToNoMoreInteractions.java @@ -0,0 +1,84 @@ +/* + * Copyright 2024 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.jspecify.annotations.Nullable; +import org.openrewrite.*; +import org.openrewrite.java.ChangeMethodName; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.dependencies.DependencyInsight; +import org.openrewrite.java.search.UsesMethod; +import org.openrewrite.java.tree.J; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class VerifyZeroToNoMoreInteractions extends ScanningRecipe { + + private static final String VERIFY_ZERO_INTERACTIONS = "org.mockito.Mockito verifyZeroInteractions(..)"; + private static final MethodMatcher ASSERT_INSTANCE_OF_MATCHER = new MethodMatcher(VERIFY_ZERO_INTERACTIONS, true); + + @Override + public String getDisplayName() { + return "Replace `verifyZeroInteractions() to `verifyNoMoreInteractions()"; + } + + @Override + public String getDescription() { + return "Replaces `verifyZeroInteractions()` with `verifyNoMoreInteractions()` in Mockito tests when migration when using a Mockito version < 3.x."; + } + + @Override + public AtomicBoolean getInitialValue(final ExecutionContext ctx) { + return new AtomicBoolean(false); + } + + @Override + public TreeVisitor getScanner(AtomicBoolean usingOlderMockito) { + TreeVisitor div = new DependencyInsight("org.mockito", "mockito-*", "[1.0,3.0)", null).getVisitor(); + return new TreeVisitor() { + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (!usingOlderMockito.get() && div.visit(tree, ctx) != tree) { + usingOlderMockito.set(true); + } + return tree; + } + }; + } + + @Override + public TreeVisitor getVisitor(AtomicBoolean usingOlderMockito) { + return Preconditions.check(usingOlderMockito.get(), + Preconditions.check(new UsesMethod<>(ASSERT_INSTANCE_OF_MATCHER), new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation md = super.visitMethodInvocation(method, ctx); + + if (!ASSERT_INSTANCE_OF_MATCHER.matches(md)) { + return md; + } + + maybeAddImport("org.mockito.Mockito", "verifyNoMoreInteractions"); + maybeRemoveImport("org.mockito.Mockito.verifyZeroInteractions"); + + ChangeMethodName changeMethodName = new ChangeMethodName(VERIFY_ZERO_INTERACTIONS, "verifyNoMoreInteractions", false, false); + return (J.MethodInvocation) changeMethodName.getVisitor().visitNonNull(md, ctx); + } + }) + ); + } +} diff --git a/src/main/java/org/openrewrite/java/testing/mockito/package-info.java b/src/main/java/org/openrewrite/java/testing/mockito/package-info.java index 208941548..3a3ec2512 100644 --- a/src/main/java/org/openrewrite/java/testing/mockito/package-info.java +++ b/src/main/java/org/openrewrite/java/testing/mockito/package-info.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@NonNullApi +@NullMarked package org.openrewrite.java.testing.mockito; -import org.openrewrite.internal.lang.NonNullApi; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/org/openrewrite/java/testing/testcontainers/ExplicitContainerImage.java b/src/main/java/org/openrewrite/java/testing/testcontainers/ExplicitContainerImage.java index 9599ae40a..e368e1e07 100644 --- a/src/main/java/org/openrewrite/java/testing/testcontainers/ExplicitContainerImage.java +++ b/src/main/java/org/openrewrite/java/testing/testcontainers/ExplicitContainerImage.java @@ -16,6 +16,7 @@ package org.openrewrite.java.testing.testcontainers; import lombok.RequiredArgsConstructor; +import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaParser; @@ -45,6 +46,7 @@ public class ExplicitContainerImage extends Recipe { @Option(displayName = "Parse image", description = "Whether to call `DockerImageName.parse(image)`.", required = false) + @Nullable private final Boolean parseImage; @Override diff --git a/src/main/java/org/openrewrite/java/testing/testcontainers/package-info.java b/src/main/java/org/openrewrite/java/testing/testcontainers/package-info.java index 956a3fb3f..4d843dc72 100644 --- a/src/main/java/org/openrewrite/java/testing/testcontainers/package-info.java +++ b/src/main/java/org/openrewrite/java/testing/testcontainers/package-info.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@NonNullApi +@NullMarked package org.openrewrite.java.testing.testcontainers; -import org.openrewrite.internal.lang.NonNullApi; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/resources/META-INF/rewrite/assertj.yml b/src/main/resources/META-INF/rewrite/assertj.yml index 2695a5dd0..302b76692 100644 --- a/src/main/resources/META-INF/rewrite/assertj.yml +++ b/src/main/resources/META-INF/rewrite/assertj.yml @@ -26,7 +26,25 @@ recipeList: - org.openrewrite.java.testing.assertj.JUnitToAssertj - org.openrewrite.java.testing.assertj.StaticImports - org.openrewrite.java.testing.assertj.SimplifyChainedAssertJAssertions - - org.openrewrite.java.testing.assertj.IsEqualToEmptyString + - org.openrewrite.java.testing.assertj.SimplifyAssertJAssertions + + - tech.picnic.errorprone.refasterrules.AssertJBigDecimalRulesRecipes + - tech.picnic.errorprone.refasterrules.AssertJBigIntegerRulesRecipes + - tech.picnic.errorprone.refasterrules.AssertJBooleanRulesRecipes + - tech.picnic.errorprone.refasterrules.AssertJByteRulesRecipes + - tech.picnic.errorprone.refasterrules.AssertJCharSequenceRulesRecipes + - tech.picnic.errorprone.refasterrules.AssertJDoubleRulesRecipes + - tech.picnic.errorprone.refasterrules.AssertJFloatRulesRecipes + - tech.picnic.errorprone.refasterrules.AssertJIntegerRulesRecipes + - tech.picnic.errorprone.refasterrules.AssertJLongRulesRecipes + - tech.picnic.errorprone.refasterrules.AssertJNumberRulesRecipes + - tech.picnic.errorprone.refasterrules.AssertJPrimitiveRulesRecipes + - tech.picnic.errorprone.refasterrules.AssertJRulesRecipes + - tech.picnic.errorprone.refasterrules.AssertJShortRulesRecipes + - tech.picnic.errorprone.refasterrules.AssertJStringRulesRecipes + - tech.picnic.errorprone.refasterrules.AssertJThrowingCallableRulesRecipes + + - org.openrewrite.java.testing.assertj.CollapseConsecutiveAssertThatStatements --- type: specs.openrewrite.org/v1beta/recipe @@ -43,6 +61,9 @@ recipeList: - org.openrewrite.java.ChangeMethodTargetToStatic: methodPattern: "org.assertj.core.api.AssertionsForInterfaceTypes assertThat(..)" fullyQualifiedTargetTypeName: "org.assertj.core.api.Assertions" + - org.openrewrite.java.ChangeMethodTargetToStatic: + methodPattern: "org.assertj.core.api.Fail fail(..)" + fullyQualifiedTargetTypeName: "org.assertj.core.api.Assertions" - org.openrewrite.java.UseStaticImport: methodPattern: "org.assertj.core.api.Assertions *(..)" @@ -50,7 +71,7 @@ recipeList: type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.testing.assertj.SimplifyChainedAssertJAssertions displayName: Simplify AssertJ chained assertions -description: Replace AssertJ chained assertions with dedicated assertions that function the same. +description: Replace AssertJ assertions where a method is called on the actual value with a dedicated assertion. tags: - testing - assertj @@ -92,16 +113,31 @@ recipeList: assertToReplace: isTrue dedicatedAssertion: startsWith requiredType: java.lang.String + - org.openrewrite.java.testing.assertj.SimplifyChainedAssertJAssertion: + chainedAssertion: startsWith + assertToReplace: isFalse + dedicatedAssertion: doesNotStartWith + requiredType: java.lang.String - org.openrewrite.java.testing.assertj.SimplifyChainedAssertJAssertion: chainedAssertion: endsWith assertToReplace: isTrue dedicatedAssertion: endsWith requiredType: java.lang.String + - org.openrewrite.java.testing.assertj.SimplifyChainedAssertJAssertion: + chainedAssertion: endsWith + assertToReplace: isFalse + dedicatedAssertion: doesNotEndWith + requiredType: java.lang.String - org.openrewrite.java.testing.assertj.SimplifyChainedAssertJAssertion: chainedAssertion: matches assertToReplace: isTrue dedicatedAssertion: matches requiredType: java.lang.String + - org.openrewrite.java.testing.assertj.SimplifyChainedAssertJAssertion: + chainedAssertion: matches + assertToReplace: isFalse + dedicatedAssertion: doesNotMatch + requiredType: java.lang.String - org.openrewrite.java.testing.assertj.SimplifyChainedAssertJAssertion: chainedAssertion: trim assertToReplace: isEmpty @@ -337,18 +373,92 @@ recipeList: assertToReplace: isSameAs dedicatedAssertion: containsSame requiredType: java.util.Optional + - org.openrewrite.java.testing.assertj.SimplifyChainedAssertJAssertion: + chainedAssertion: hasNext + assertToReplace: isTrue + dedicatedAssertion: hasNext + requiredType: java.util.Iterator + - org.openrewrite.java.testing.assertj.SimplifyChainedAssertJAssertion: + chainedAssertion: hasNext + assertToReplace: isFalse + dedicatedAssertion: isExhausted + requiredType: java.util.Iterator + # Object assertions + - org.openrewrite.java.testing.assertj.SimplifyChainedAssertJAssertion: + chainedAssertion: toString + assertToReplace: isEqualTo + dedicatedAssertion: hasToString + requiredType: java.lang.Object + - org.openrewrite.java.testing.assertj.SimplifyChainedAssertJAssertion: + chainedAssertion: equals + assertToReplace: isTrue + dedicatedAssertion: isEqualTo + requiredType: java.lang.Object + +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.testing.assertj.SimplifyAssertJAssertions +displayName: Shorten AssertJ assertions +description: Replace AssertJ assertions where a dedicated assertion is available for the same actual value. +tags: + - testing + - assertj +recipeList: + - org.openrewrite.java.testing.assertj.SimplifyAssertJAssertion: + assertToReplace: isEqualTo + literalArgument: "null" + dedicatedAssertion: isNull + requiredType: java.lang.Object + - org.openrewrite.java.testing.assertj.SimplifyAssertJAssertion: + assertToReplace: isEqualTo + literalArgument: true + dedicatedAssertion: isTrue + requiredType: java.lang.Boolean + - org.openrewrite.java.testing.assertj.SimplifyAssertJAssertion: + assertToReplace: isEqualTo + literalArgument: '""' + dedicatedAssertion: isEmpty + requiredType: java.lang.String + - org.openrewrite.java.testing.assertj.SimplifyAssertJAssertion: + assertToReplace: isEqualTo + literalArgument: false + dedicatedAssertion: isFalse + requiredType: java.lang.Boolean + - org.openrewrite.java.testing.assertj.SimplifyAssertJAssertion: + assertToReplace: hasSize + literalArgument: 0 + dedicatedAssertion: isEmpty + requiredType: java.lang.String + - org.openrewrite.java.testing.assertj.SimplifyAssertJAssertion: + assertToReplace: hasSize + literalArgument: 0 + dedicatedAssertion: isEmpty + requiredType: java.io.File + - org.openrewrite.java.testing.assertj.SimplifyAssertJAssertion: + assertToReplace: hasSize + literalArgument: 0 + dedicatedAssertion: isEmpty + requiredType: java.util.Collection + - org.openrewrite.java.testing.assertj.SimplifyAssertJAssertion: + assertToReplace: hasSize + literalArgument: 0 + dedicatedAssertion: isEmpty + requiredType: java.util.Map --- type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.testing.assertj.JUnitToAssertj displayName: Migrate JUnit asserts to AssertJ -description: AssertJ provides a rich set of assertions, truly helpful error messages, improves test code readability. Converts assertions from `org.junit.jupiter.api.Assertions` to `org.assertj.core.api.Assertions`. +description: >- + AssertJ provides a rich set of assertions, truly helpful error messages, improves test code readability. + Converts assertions from `org.junit.jupiter.api.Assertions` to `org.assertj.core.api.Assertions`. + Will convert JUnit 4 to JUnit Jupiter if necessary to match and modify assertions. tags: - testing - assertj recipeList: # First improve the assertions for JUnit, to fix inverted expected/actual values - - org.openrewrite.java.testing.junit5.CleanupAssertions + - org.openrewrite.java.testing.junit5.JUnit5BestPractices - org.openrewrite.java.testing.assertj.JUnitAssertArrayEqualsToAssertThat - org.openrewrite.java.testing.assertj.JUnitAssertEqualsToAssertThat - org.openrewrite.java.testing.assertj.JUnitAssertFalseToAssertThat @@ -359,9 +469,11 @@ recipeList: - org.openrewrite.java.testing.assertj.JUnitAssertTrueToAssertThat - org.openrewrite.java.testing.assertj.JUnitFailToAssertJFail - org.openrewrite.java.testing.assertj.JUnitAssertThrowsToAssertExceptionType + - org.openrewrite.java.testing.assertj.JUnitAssertInstanceOfToAssertThat - org.openrewrite.java.dependencies.AddDependency: groupId: org.assertj artifactId: assertj-core version: 3.x onlyIfUsing: org.assertj.core.api.Assertions acceptTransitive: true + - tech.picnic.errorprone.refasterrules.JUnitToAssertJRulesRecipes diff --git a/src/main/resources/META-INF/rewrite/classpath/jmockit-1.22.jar b/src/main/resources/META-INF/rewrite/classpath/jmockit-1.22.jar new file mode 100644 index 000000000..9b3cc6718 Binary files /dev/null and b/src/main/resources/META-INF/rewrite/classpath/jmockit-1.22.jar differ diff --git a/src/main/resources/META-INF/rewrite/classpath/rider-junit5-1.44.0.jar b/src/main/resources/META-INF/rewrite/classpath/rider-junit5-1.44.0.jar new file mode 100644 index 000000000..8c4bbfc62 Binary files /dev/null and b/src/main/resources/META-INF/rewrite/classpath/rider-junit5-1.44.0.jar differ diff --git a/src/main/resources/META-INF/rewrite/dbrider.yml b/src/main/resources/META-INF/rewrite/dbrider.yml new file mode 100644 index 000000000..a6ecc2980 --- /dev/null +++ b/src/main/resources/META-INF/rewrite/dbrider.yml @@ -0,0 +1,32 @@ +# +# Copyright 2024 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. +# +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.testing.dbrider.MigrateDbRiderSpringToDbRiderJUnit5 +displayName: Migrate rider-spring (JUnit4) to rider-junit5 (JUnit5) +description: This recipe will migrate the necessary dependencies and annotations from DbRider with JUnit4 to JUnit5 in a Spring application. +tags: + - testing + - dbrider + - spring +recipeList: + - org.openrewrite.java.testing.dbrider.ExecutionListenerToDbRiderAnnotation + - org.openrewrite.java.dependencies.ChangeDependency: + oldGroupId: com.github.database-rider + oldArtifactId: rider-spring + newArtifactId: rider-junit5 + newVersion: 1.x +--- \ No newline at end of file diff --git a/src/main/resources/META-INF/rewrite/hamcrest.yml b/src/main/resources/META-INF/rewrite/hamcrest.yml index 7439f28ce..2271ea278 100644 --- a/src/main/resources/META-INF/rewrite/hamcrest.yml +++ b/src/main/resources/META-INF/rewrite/hamcrest.yml @@ -43,6 +43,9 @@ recipeList: - org.openrewrite.java.ChangeMethodTargetToStatic: methodPattern: org.hamcrest.core.* *(..) fullyQualifiedTargetTypeName: org.hamcrest.Matchers + - org.openrewrite.java.ChangeMethodTargetToStatic: + methodPattern: org.hamcrest.collection.* *(..) + fullyQualifiedTargetTypeName: org.hamcrest.Matchers # Then remove wrapping `is(Matcher)` calls such that further recipes will match - org.openrewrite.java.testing.hamcrest.RemoveIsMatcher @@ -64,6 +67,40 @@ recipeList: matcher: comparesEqualTo assertion: isEqualByComparingTo + - org.openrewrite.java.testing.hamcrest.HamcrestMatcherToAssertJ: + matcher: lessThan + assertion: isBefore + argumentType: java.util.Date + - org.openrewrite.java.testing.hamcrest.HamcrestMatcherToAssertJ: + matcher: lessThanOrEqualTo + assertion: isBeforeOrEqualTo + argumentType: java.util.Date + - org.openrewrite.java.testing.hamcrest.HamcrestMatcherToAssertJ: + matcher: greaterThan + assertion: isAfter + argumentType: java.util.Date + - org.openrewrite.java.testing.hamcrest.HamcrestMatcherToAssertJ: + matcher: greaterThanOrEqualTo + assertion: isAfterOrEqualTo + argumentType: java.util.Date + + - org.openrewrite.java.testing.hamcrest.HamcrestMatcherToAssertJ: + matcher: lessThan + assertion: isBefore + argumentType: java.time.Instant + - org.openrewrite.java.testing.hamcrest.HamcrestMatcherToAssertJ: + matcher: lessThanOrEqualTo + assertion: isBeforeOrEqualTo + argumentType: java.time.Instant + - org.openrewrite.java.testing.hamcrest.HamcrestMatcherToAssertJ: + matcher: greaterThan + assertion: isAfter + argumentType: java.time.Instant + - org.openrewrite.java.testing.hamcrest.HamcrestMatcherToAssertJ: + matcher: greaterThanOrEqualTo + assertion: isAfterOrEqualTo + argumentType: java.time.Instant + - org.openrewrite.java.testing.hamcrest.HamcrestMatcherToAssertJ: matcher: equalTo assertion: isEqualTo diff --git a/src/main/resources/META-INF/rewrite/jmockit.yml b/src/main/resources/META-INF/rewrite/jmockit.yml index dd7d1367a..d1ee335d7 100644 --- a/src/main/resources/META-INF/rewrite/jmockit.yml +++ b/src/main/resources/META-INF/rewrite/jmockit.yml @@ -22,8 +22,9 @@ tags: - testing - jmockit recipeList: - - org.openrewrite.java.testing.jmockit.JMockitExpectationsToMockito - - org.openrewrite.java.testing.jmockit.JMockitMockedVariableToMockito + - org.openrewrite.java.testing.jmockit.JMockitBlockToMockito + - org.openrewrite.java.testing.jmockit.JMockitMockUpToMockito + - org.openrewrite.java.testing.jmockit.JMockitAnnotatedArgumentToMockito - org.openrewrite.java.ChangeType: oldFullyQualifiedTypeName: mockit.Mocked newFullyQualifiedTypeName: org.mockito.Mock @@ -36,6 +37,9 @@ recipeList: - org.openrewrite.java.ChangeType: oldFullyQualifiedTypeName: mockit.integration.junit5.JMockitExtension newFullyQualifiedTypeName: org.mockito.junit.jupiter.MockitoExtension + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: mockit.integration.junit4.JMockit + newFullyQualifiedTypeName: org.mockito.junit.MockitoJUnitRunner - org.openrewrite.java.dependencies.AddDependency: groupId: org.mockito artifactId: mockito-core diff --git a/src/main/resources/META-INF/rewrite/junit5.yml b/src/main/resources/META-INF/rewrite/junit5.yml index 4b28417a3..481a4d71d 100755 --- a/src/main/resources/META-INF/rewrite/junit5.yml +++ b/src/main/resources/META-INF/rewrite/junit5.yml @@ -25,12 +25,14 @@ recipeList: - org.openrewrite.java.testing.junit5.JUnit4to5Migration - org.openrewrite.java.testing.junit5.StaticImports - org.openrewrite.java.testing.junit5.CleanupAssertions + - org.openrewrite.java.testing.cleanup.AssertLiteralBooleanToFailRecipe - org.openrewrite.java.testing.cleanup.RemoveTestPrefix - org.openrewrite.java.testing.cleanup.TestsShouldNotBePublic - org.openrewrite.java.testing.junit5.AddParameterizedTestAnnotation - org.openrewrite.java.testing.junit5.RemoveDuplicateTestTemplates - org.openrewrite.java.testing.junit5.RemoveTryCatchFailBlocks - org.openrewrite.java.testing.junit5.LifecycleNonPrivate + - org.openrewrite.java.testing.junit5.AssertThrowsOnLastStatement --- type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.testing.junit5.StaticImports @@ -50,6 +52,10 @@ description: Migrates JUnit 4.x tests to JUnit Jupiter. tags: - testing - junit +preconditions: + - org.openrewrite.java.search.DoesNotUseType: + fullyQualifiedTypeName: org.testng..* + includeImplicit: true recipeList: - org.openrewrite.java.testing.junit5.UseWiremockExtension - org.openrewrite.java.testing.junit5.IgnoreToDisabled @@ -64,6 +70,7 @@ recipeList: groupId: org.apache.maven.surefire artifactId: surefire-junit* - org.openrewrite.java.testing.junit5.UpgradeSurefirePlugin + - org.openrewrite.java.testing.junit5.AddHamcrestJUnitDependency - org.openrewrite.java.testing.junit5.UseHamcrestAssertThat - org.openrewrite.java.testing.junit5.MigrateAssumptions - org.openrewrite.java.testing.junit5.UseMockitoExtension @@ -99,7 +106,7 @@ recipeList: # Workaround for https://github.com/testcontainers/testcontainers-java/issues/970: - org.openrewrite.maven.RemoveExclusion: groupId: org.testcontainers - artifactId: testcontainers + artifactId: '*' exclusionGroupId: junit exclusionArtifactId: junit # Similar for https://github.com/openrewrite/rewrite-testing-frameworks/issues/477 @@ -122,18 +129,6 @@ recipeList: onlyIfUsing: org.junit.jupiter.params.ParameterizedTest acceptTransitive: true scope: test - - org.openrewrite.java.dependencies.UpgradeDependencyVersion: - groupId: org.mockito - artifactId: "*" - newVersion: 3.x - overrideManagedVersion: false - - org.openrewrite.java.dependencies.AddDependency: - groupId: org.mockito - artifactId: mockito-junit-jupiter - version: 3.x - onlyIfUsing: org.mockito..* - acceptTransitive: true - scope: test - org.openrewrite.maven.UpgradePluginVersion: groupId: org.apache.maven.plugins artifactId: maven-surefire-plugin @@ -143,6 +138,10 @@ recipeList: artifactId: maven-failsafe-plugin newVersion: 3.1.x - org.openrewrite.java.testing.junit5.GradleUseJunitJupiter + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: org.jbehave.core.junit.JUnitStories + newFullyQualifiedTypeName: org.jbehave.core.junit.JupiterStories + - org.openrewrite.java.testing.dbrider.MigrateDbRiderSpringToDbRiderJUnit5 --- type: specs.openrewrite.org/v1beta/recipe @@ -170,13 +169,6 @@ tags: - junit - hamcrest recipeList: - - org.openrewrite.java.dependencies.AddDependency: - groupId: org.hamcrest - artifactId: hamcrest-junit - version: 2.x - scope: test - onlyIfUsing: org.junit.Assume - acceptTransitive: true - org.openrewrite.java.ChangeMethodTargetToStatic: methodPattern: org.junit.Assume assumeThat(..) fullyQualifiedTargetTypeName: org.hamcrest.junit.MatcherAssume @@ -258,7 +250,7 @@ recipeList: type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.testing.junit5.UpgradeOkHttpMockWebServer displayName: Use OkHttp 3 MockWebServer for JUnit 5 -description: Migrates OkHttp 3 `MockWebServer` to enable JUnit Jupiter Extension support +description: Migrates OkHttp 3 `MockWebServer` to enable JUnit Jupiter Extension support. tags: - testing - junit @@ -277,7 +269,7 @@ recipeList: type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.testing.junit5.CleanupAssertions displayName: Clean Up Assertions -description: Simplifies JUnit Jupiter assertions to their most-direct equivalents +description: Simplifies JUnit Jupiter assertions to their most-direct equivalents. tags: - testing - junit @@ -289,6 +281,7 @@ recipeList: - org.openrewrite.java.testing.cleanup.AssertFalseEqualsToAssertNotEquals - org.openrewrite.java.testing.cleanup.AssertEqualsNullToAssertNull - org.openrewrite.java.testing.cleanup.AssertFalseNullToAssertNotNull + - org.openrewrite.java.testing.cleanup.AssertTrueNullToAssertNull - org.openrewrite.java.testing.cleanup.AssertEqualsBooleanToAssertBoolean - org.openrewrite.java.testing.cleanup.AssertNotEqualsBooleanToAssertBoolean - org.openrewrite.java.testing.cleanup.AssertionsArgumentOrder @@ -296,7 +289,7 @@ recipeList: type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.testing.junit5.UseXMLUnitLegacy displayName: Use XMLUnit Legacy for JUnit 5 -description: Migrates XMLUnit 1.x to XMLUnit legacy 2.x +description: Migrates XMLUnit 1.x to XMLUnit legacy 2.x. tags: - testing - junit diff --git a/src/main/resources/META-INF/rewrite/mockito.yml b/src/main/resources/META-INF/rewrite/mockito.yml index ca1194067..e75ef742d 100644 --- a/src/main/resources/META-INF/rewrite/mockito.yml +++ b/src/main/resources/META-INF/rewrite/mockito.yml @@ -15,9 +15,33 @@ # --- type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.testing.mockito.MockitoBestPractices +displayName: Mockito best practices +description: Applies best practices for Mockito tests. +tags: + - testing + - mockito +recipeList: + - org.openrewrite.java.testing.mockito.Mockito1to5Migration + - org.openrewrite.java.RemoveAnnotation: + annotationPattern: "@org.mockito.junit.jupiter.MockitoSettings(strictness=org.mockito.quality.Strictness.WARN)" + - org.openrewrite.java.testing.mockito.SimplifyMockitoVerifyWhenGiven +--- +type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.testing.mockito.Mockito1to5Migration displayName: Mockito 5.x upgrade description: Upgrade Mockito from 1.x to 5.x. +tags: + - testing + - mockito +recipeList: + - org.openrewrite.java.testing.mockito.Mockito1to4Migration + - org.openrewrite.java.testing.mockito.Mockito4to5Only +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.testing.mockito.Mockito4to5Only +displayName: Mockito 4 to 5.x upgrade only +description: Upgrade Mockito from 4.x to 5.x. Does not include 1.x to 4.x migration. tags: - testing - mockito @@ -31,8 +55,11 @@ recipeList: groupId: org.mockito artifactId: "*" newVersion: 5.x + - org.openrewrite.java.dependencies.UpgradeDependencyVersion: + groupId: net.bytebuddy + artifactId: byte-buddy* + newVersion: 1.15.x - org.openrewrite.maven.RemoveDuplicateDependencies - --- type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.testing.mockito.Mockito1to4Migration @@ -43,10 +70,15 @@ tags: - mockito recipeList: - org.openrewrite.java.testing.mockito.Mockito1to3Migration + - org.openrewrite.java.testing.mockito.MockitoWhenOnStaticToMockStatic - org.openrewrite.java.dependencies.UpgradeDependencyVersion: groupId: org.mockito artifactId: "*" newVersion: 4.x + - org.openrewrite.java.dependencies.UpgradeDependencyVersion: + groupId: net.bytebuddy + artifactId: byte-buddy* + newVersion: 1.12.19 --- type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.testing.mockito.Mockito1to3Migration @@ -110,6 +142,7 @@ recipeList: oldParameterNames: - mode - verification + - org.openrewrite.java.testing.mockito.VerifyZeroToNoMoreInteractions - org.openrewrite.java.ChangeMethodName: methodPattern: org.mockito.Mockito verifyZeroInteractions(..) newMethodName: verifyNoInteractions @@ -131,13 +164,12 @@ recipeList: - org.openrewrite.java.testing.mockito.CleanupMockitoImports - org.openrewrite.java.testing.mockito.MockUtilsToStatic - org.openrewrite.java.testing.junit5.MockitoJUnitToMockitoExtension -# https://github.com/openrewrite/rewrite-testing-frameworks/issues/360 -# - org.openrewrite.java.testing.mockito.ReplacePowerMockito + - org.openrewrite.java.testing.mockito.ReplacePowerMockito - org.openrewrite.java.dependencies.AddDependency: groupId: org.mockito artifactId: mockito-junit-jupiter version: 3.x - onlyIfUsing: org.mockito.junit.jupiter.MockitoExtension + onlyIfUsing: org.mockito.junit.jupiter.* acceptTransitive: true - org.openrewrite.java.dependencies.UpgradeDependencyVersion: groupId: org.mockito @@ -147,3 +179,7 @@ recipeList: oldGroupId: org.mockito oldArtifactId: mockito-all newArtifactId: mockito-core + - org.openrewrite.java.dependencies.UpgradeDependencyVersion: + groupId: net.bytebuddy + artifactId: byte-buddy* + newVersion: 1.11.13 diff --git a/src/main/resources/META-INF/rewrite/powermockito.yml b/src/main/resources/META-INF/rewrite/powermockito.yml index 84b11e652..c27c80d7e 100644 --- a/src/main/resources/META-INF/rewrite/powermockito.yml +++ b/src/main/resources/META-INF/rewrite/powermockito.yml @@ -18,41 +18,33 @@ type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.testing.mockito.ReplacePowerMockito displayName: Replace PowerMock with raw Mockito -description: Upgrade to Mockito 5 and replace PowerMockito with raw Mockito. +description: PowerMockito with raw Mockito; best executed as part of a Mockito upgrade. tags: - testing - mockito recipeList: - - org.openrewrite.java.testing.mockito.Mockito1to5Migration - org.openrewrite.java.RemoveAnnotation: annotationPattern: "@org.powermock.core.classloader.annotations.PowerMockIgnore" - org.openrewrite.java.ChangeMethodTargetToStatic: methodPattern: org.powermock.api.mockito.PowerMockito mockStatic(..) fullyQualifiedTargetTypeName: org.mockito.Mockito returnType: org.mockito.MockedStatic - matchOverrides: null - matchUnknownTypes: - org.openrewrite.java.ChangeMethodTargetToStatic: methodPattern: org.powermock.api.mockito.PowerMockito do*(..) fullyQualifiedTargetTypeName: org.mockito.Mockito - matchOverrides: null - matchUnknownTypes: null - org.openrewrite.java.ChangeMethodTargetToStatic: methodPattern: org.powermock.api.mockito.PowerMockito mock(..) fullyQualifiedTargetTypeName: org.mockito.Mockito - matchOverrides: null - matchUnknownTypes: null - org.openrewrite.java.ChangeMethodTargetToStatic: methodPattern: org.powermock.api.mockito.PowerMockito spy(..) fullyQualifiedTargetTypeName: org.mockito.Mockito - matchOverrides: null - matchUnknownTypes: null - org.openrewrite.java.ChangeMethodTargetToStatic: methodPattern: org.powermock.api.mockito.PowerMockito when(..) fullyQualifiedTargetTypeName: org.mockito.Mockito - matchOverrides: null - matchUnknownTypes: null - org.openrewrite.java.testing.mockito.PowerMockitoMockStaticToMockito - org.openrewrite.java.dependencies.RemoveDependency: groupId: org.powermock artifactId: powermock-api-mockito* + - org.openrewrite.java.dependencies.RemoveDependency: + groupId: org.powermock + artifactId: powermock-core diff --git a/src/test/java/org/openrewrite/java/testing/.editorconfig b/src/test/java/org/openrewrite/java/testing/.editorconfig deleted file mode 100644 index a4824935e..000000000 --- a/src/test/java/org/openrewrite/java/testing/.editorconfig +++ /dev/null @@ -1,5 +0,0 @@ -root = true - -[*.java] -indent_size = 4 -ij_continuation_indent_size = 2 diff --git a/src/test/java/org/openrewrite/java/testing/assertj/AssertJBestPracticesTest.java b/src/test/java/org/openrewrite/java/testing/assertj/AssertJBestPracticesTest.java new file mode 100644 index 000000000..a23205627 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/assertj/AssertJBestPracticesTest.java @@ -0,0 +1,323 @@ +/* + * Copyright 2024 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.assertj; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.openrewrite.DocumentExample; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Issue; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import java.util.stream.Stream; + +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.openrewrite.java.Assertions.java; + +class AssertJBestPracticesTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.parser( + JavaParser.fromJavaVersion() + .classpathFromResources(new InMemoryExecutionContext(), "assertj-core-3.24")) + .recipeFromResources("org.openrewrite.java.testing.assertj.Assertj"); + } + + @DocumentExample + @Test + @SuppressWarnings("DataFlowIssue") + void convertsIsEqualToEmptyString() { + rewriteRun( + // language=java + java( + """ + import static org.assertj.core.api.Assertions.assertThat; + class Test { + void test() { + assertThat("test").isEqualTo(""); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + class Test { + void test() { + assertThat("test").isEmpty(); + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/398") + void sizeIsEqualToZeroToIsEmpty() { + //language=java + rewriteRun( + java( + """ + import java.util.List; + + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void testMethod(List list) { + assertThat(list.size()).isEqualTo(0); + } + } + """, + """ + import java.util.List; + + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void testMethod(List list) { + assertThat(list).isEmpty(); + } + } + """ + ) + ); + } + + /** + * Chained AssertJ assertions should be simplified to the corresponding dedicated assertion, as + * per java:S5838 + */ + @Nested + class SonarDedicatedAssertions { + private static Stream replacements() { + return Stream.of( + // Related to Object + arguments("Object", "assertThat(x).isEqualTo(null)", "assertThat(x).isNull()"), + arguments("Boolean", "assertThat(x).isEqualTo(true)", "assertThat(x).isTrue()"), + arguments("Boolean", "assertThat(x).isEqualTo(false)", "assertThat(x).isFalse()"), + arguments("Object", "assertThat(x.equals(y)).isTrue()", "assertThat(x).isEqualTo(y)"), +// arguments("Object", "assertThat(x == y).isTrue()", "assertThat(x).isSameAs(y)"), +// arguments("Object", "assertThat(x == null).isTrue()", "assertThat(x).isNull()"), + arguments( + "Object", + "assertThat(x.toString()).isEqualTo(\"y\")", + "assertThat(x).hasToString(\"y\")"), +// arguments( +// "Object", +// "assertThat(x.hashCode()).isEqualTo(y.hashCode())", +// "assertThat(x).hasSameHashCodeAs(y)"), +// arguments( +// "Object", +// "assertThat(x instanceof String).isTrue()", +// "assertThat(x).isInstanceOf(String.class)"), + // Related to Comparable +// arguments( +// "java.math.BigDecimal", +// "assertThat(x.compareTo(y)).isZero()", +// "assertThat(x).isEqualByComparingTo(y)"), +// arguments( +// "int", "assertThat(x >= y).isTrue()", "assertThat(x).isGreaterThanOrEqualTo(y)"), +// arguments("long", "assertThat(x > y).isTrue()", "assertThat(x).isGreaterThan(y)"), +// arguments( +// "double", "assertThat(x <= y).isTrue()", "assertThat(x).isLessThanOrEqualTo(y)"), +// arguments("float", "assertThat(x < y).isTrue()", "assertThat(x).isLessThan(y)"), + // Related to String + arguments("String", "assertThat(x.isEmpty()).isTrue()", "assertThat(x).isEmpty()"), + arguments("String", "assertThat(x).hasSize(0)", "assertThat(x).isEmpty()"), + arguments("String", "assertThat(x.equals(y)).isTrue()", "assertThat(x).isEqualTo(y)"), + arguments( + "String", + "assertThat(x.equalsIgnoreCase(y)).isTrue()", + "assertThat(x).isEqualToIgnoringCase(y)"), + arguments("String", "assertThat(x.contains(y)).isTrue()", "assertThat(x).contains(y)"), + arguments( + "String", "assertThat(x.startsWith(y)).isTrue()", "assertThat(x).startsWith(y)"), + arguments("String", "assertThat(x.endsWith(y)).isTrue()", "assertThat(x).endsWith(y)"), + arguments("String", "assertThat(x.matches(y)).isTrue()", "assertThat(x).matches(y)"), + arguments("String", "assertThat(x.trim()).isEmpty()", "assertThat(x).isBlank()"), + arguments("String", "assertThat(x.length()).isEqualTo(5)", "assertThat(x).hasSize(5)"), +// arguments("String", "assertThat(x).hasSize(y.length())", "assertThat(x).hasSameSizeAs(y)"), + // Related to File + arguments("java.io.File", "assertThat(x).hasSize(0)", "assertThat(x).isEmpty()"), + arguments("java.io.File", "assertThat(x.length()).isZero()", "assertThat(x).isEmpty()"), + arguments( + "java.io.File", "assertThat(x.length()).isEqualTo(3)", "assertThat(x).hasSize(3)"), + arguments("java.io.File", "assertThat(x.canRead()).isTrue()", "assertThat(x).canRead()"), + arguments( + "java.io.File", "assertThat(x.canWrite()).isTrue()", "assertThat(x).canWrite()"), + arguments("java.io.File", "assertThat(x.exists()).isTrue()", "assertThat(x).exists()"), + arguments( + "java.io.File", + "assertThat(x.getName()).isEqualTo(\"a\")", + "assertThat(x).hasName(\"a\")"), + arguments( + "java.io.File", + "assertThat(x.getParent()).isEqualTo(\"b\")", + "assertThat(x).hasParent(\"b\")"), + arguments( + "java.io.File", + "assertThat(x.getParentFile()).isNull()", + "assertThat(x).hasNoParent()"), + arguments( + "java.io.File", "assertThat(x.isAbsolute()).isTrue()", "assertThat(x).isAbsolute()"), + arguments( + "java.io.File", "assertThat(x.isAbsolute()).isFalse()", "assertThat(x).isRelative()"), + arguments( + "java.io.File", + "assertThat(x.isDirectory()).isTrue()", + "assertThat(x).isDirectory()"), + arguments("java.io.File", "assertThat(x.isFile()).isTrue()", "assertThat(x).isFile()"), + arguments( + "java.io.File", "assertThat(x.list()).isEmpty()", "assertThat(x).isEmptyDirectory()"), + // Related to Path + arguments( + "java.nio.file.Path", + "assertThat(x.startsWith(\"x\")).isTrue()", + "assertThat(x).startsWithRaw(Path.of(\"x\"))"), + arguments( + "java.nio.file.Path", + "assertThat(x.endsWith(\"y\")).isTrue()", + "assertThat(x).endsWithRaw(Path.of(\"y\"))"), + arguments( + "java.nio.file.Path", + "assertThat(x.getParent()).isEqualTo(y)", + "assertThat(x).hasParentRaw(y)"), + arguments( + "java.nio.file.Path", + "assertThat(x.getParent()).isNull()", + "assertThat(x).hasNoParentRaw()"), + arguments( + "java.nio.file.Path", + "assertThat(x.isAbsolute()).isTrue()", + "assertThat(x).isAbsolute()"), + arguments( + "java.nio.file.Path", + "assertThat(x.isAbsolute()).isFalse()", + "assertThat(x).isRelative()"), + // Related to Array +// arguments("Object[]", "assertThat(x.length).isZero()", "assertThat(x).isEmpty()"), +// arguments("String[]", "assertThat(x.length).isEqualTo(7)", "assertThat(x).hasSize(7)"), +// arguments( +// "int[]", +// "assertThat(x.length).isEqualTo(y.length)", +// "assertThat(x).hasSameSizeAs(y)"), +// arguments( +// "boolean[]", +// "assertThat(x.length).isLessThanOrEqualTo(2)", +// "assertThat(x).hasSizeLessThanOrEqualTo(2)"), +// arguments( +// "double[]", "assertThat(x.length).isLessThan(5)", "assertThat(x).hasSizeLessThan(5)"), +// arguments( +// "long[]", +// "assertThat(x.length).isGreaterThan(4)", +// "assertThat(x).hasSizeGreaterThan(4)"), +// arguments( +// "char[]", +// "assertThat(x.length).isGreaterThanOrEqualTo(1)", +// "assertThat(x).hasSizeGreaterThanOrEqualTo(1)"), + // Related to Collection + arguments( + "java.util.Collection", + "assertThat(x.isEmpty()).isTrue()", + "assertThat(x).isEmpty()"), + arguments( + "java.util.Collection", + "assertThat(x.size()).isZero()", + "assertThat(x).isEmpty()"), + arguments( + "java.util.Collection", + "assertThat(x.contains(\"f\")).isTrue()", + "assertThat(x).contains(\"f\")"), + arguments( + "java.util.Collection", + "assertThat(x.containsAll(y)).isTrue()", + "assertThat(x).containsAll(y)"), + // Related to Map +// arguments( +// "java.util.Map", +// "assertThat(x).hasSize(y.size())", +// "assertThat(x).hasSameSizeAs(y)"), + arguments( + "java.util.Map", + "assertThat(x.containsKey(\"b\")).isTrue()", + "assertThat(x).containsKey(\"b\")"), + arguments( + "java.util.Map", + "assertThat(x.keySet()).contains(\"b\")", + "assertThat(x).containsKey(\"b\")"), + arguments( + "java.util.Map", + "assertThat(x.keySet()).containsOnly(\"a\")", + "assertThat(x).containsOnlyKeys(\"a\")"), + arguments( + "java.util.Map", + "assertThat(x.containsValue(value)).isTrue()", + "assertThat(x).containsValue(value)"), + arguments( + "java.util.Map", + "assertThat(x.values()).contains(value)", + "assertThat(x).containsValue(value)"), + arguments( + "java.util.Map", + "assertThat(x.get(\"a\")).isEqualTo(value)", + "assertThat(x).containsEntry(\"a\", value)"), + // Related to Optional + arguments( + "java.util.Optional", + "assertThat(x.isPresent()).isTrue()", + "assertThat(x).isPresent()"), + arguments( + "java.util.Optional", + "assertThat(x.get()).isEqualTo(value)", + "assertThat(x).contains(value)"), + arguments( + "java.util.Optional", + "assertThat(x.get()).isSameAs(value)", + "assertThat(x).containsSame(value)")); + } + + @ParameterizedTest + @MethodSource("replacements") + void sonarReplacements( + String argumentsType, String assertToReplace, String dedicatedAssertion) { + String template = + """ + import %1$s; + import static org.assertj.core.api.Assertions.assertThat; + + class A { + void test(%2$s x, %2$s y, Object value) { + %3$s; + } + } + """; + String imprt = argumentsType + .replaceAll("^([A-Z])", "java.lang.$1") + .replaceAll("<.*>", ""); + rewriteRun( + java( + template.formatted(imprt, argumentsType, assertToReplace), + template.formatted(imprt, argumentsType, dedicatedAssertion))); + } + } + +} diff --git a/src/test/java/org/openrewrite/java/testing/assertj/CollapseConsecutiveAssertThatStatementsTest.java b/src/test/java/org/openrewrite/java/testing/assertj/CollapseConsecutiveAssertThatStatementsTest.java new file mode 100644 index 000000000..fdf25e35a --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/assertj/CollapseConsecutiveAssertThatStatementsTest.java @@ -0,0 +1,320 @@ +/* + * Copyright 2024 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.assertj; + +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 CollapseConsecutiveAssertThatStatementsTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec + .parser(JavaParser.fromJavaVersion() + .classpathFromResources(new InMemoryExecutionContext(), "assertj-core-3.24")) + .recipe(new CollapseConsecutiveAssertThatStatements()); + } + + @DocumentExample + @Test + void collapseIfConsecutiveAssertThatPresent() { + //language=java + rewriteRun( + java( + """ + import java.util.Arrays; + import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void test() { + List listA = Arrays.asList("a", "b", "c"); + assertThat(listA).isNotNull(); + assertThat(listA).hasSize(3); + assertThat(listA).containsExactly("a", "b", "c"); + } + private int[] notification() { + return new int[]{1, 2, 3}; + } + } + """, + """ + import java.util.Arrays; + import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void test() { + List listA = Arrays.asList("a", "b", "c"); + assertThat(listA) + .isNotNull() + .hasSize(3) + .containsExactly("a", "b", "c"); + } + private int[] notification() { + return new int[]{1, 2, 3}; + } + } + """ + ) + ); + } + + @Test + void collapseIfMultipleConsecutiveAssertThatPresent() { + //language=java + rewriteRun( + java( + """ + import java.util.Arrays; + import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void test() { + List listA = Arrays.asList("a", "b", "c"); + // Comment nor whitespace below duplicated + assertThat(listA).isNotNull(); + assertThat(listA).hasSize(3); + assertThat(listA).containsExactly("a", "b", "c"); + + List listB = Arrays.asList("a", "b", "c"); + + assertThat(listB).isNotNull(); + assertThat(listB).hasSize(3); + } + + private int[] notification() { + return new int[]{1, 2, 3}; + } + } + """, + """ + import java.util.Arrays; + import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void test() { + List listA = Arrays.asList("a", "b", "c"); + // Comment nor whitespace below duplicated + assertThat(listA) + .isNotNull() + .hasSize(3) + .containsExactly("a", "b", "c"); + + List listB = Arrays.asList("a", "b", "c"); + + assertThat(listB) + .isNotNull() + .hasSize(3); + } + + private int[] notification() { + return new int[]{1, 2, 3}; + } + } + """ + ) + ); + } + + @Test + void collapseIfMultipleConsecutiveAssertThatPresent2() { + //language=java + rewriteRun( + java( + """ + import java.util.Arrays; + import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest2 { + void test() { + List listA = Arrays.asList("a", "b", "c"); + assertThat(listA).isNotNull(); + assertThat(listA).hasSize(3); + List listB = Arrays.asList("a", "b", "c"); + assertThat(listA).containsExactly("a", "b", "c"); + assertThat(listB).isNotNull(); + assertThat(listB).hasSize(3); + } + + private int[] notification() { + return new int[]{1, 2, 3}; + } + } + """, + """ + import java.util.Arrays; + import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest2 { + void test() { + List listA = Arrays.asList("a", "b", "c"); + assertThat(listA) + .isNotNull() + .hasSize(3); + List listB = Arrays.asList("a", "b", "c"); + assertThat(listA).containsExactly("a", "b", "c"); + assertThat(listB) + .isNotNull() + .hasSize(3); + } + + private int[] notification() { + return new int[]{1, 2, 3}; + } + } + """ + ) + ); + } + + @Test + void ignoreIfAssertThatOnDifferentVariables() { + //language=java + rewriteRun( + java( + """ + import java.util.Arrays; + import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void test() { + List listA = Arrays.asList("a", "b", "c"); + List listB = Arrays.asList("a", "b", "c"); + assertThat(listA).isNotNull(); + assertThat(listB).containsExactly("a", "b", "c"); + } + + private int[] notification() { + return new int[]{1, 2, 3}; + } + } + """ + ) + ); + } + + @Test + void ignoreIfAssertThatOnMethodInvocation() { + //language=java + rewriteRun( + java( + """ + import java.util.Arrays; + import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void test() { + assertThat(notification()).isNotNull(); + assertThat(notification()).isTrue(); + } + private boolean notification() { + return true; + } + } + """ + ) + ); + } + + @Test + void ignoreIfAssertThatChainExists() { + //language=java + rewriteRun( + java( + """ + import java.util.Arrays; + import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void test() { + List listA = Arrays.asList("a", "b", "c"); + assertThat(listA).containsExactly("a", "b", "c"); + assertThat(listA) + .isNotNull() + .hasSize(3); + assertThat(listA).containsExactly("a", "b", "c"); + } + private int[] notification() { + return new int[]{1, 2, 3}; + } + } + """ + ) + ); + } + + @Test + void ignoreIfStatementPresentBetweenTwoAssertThat() { + //language=java + rewriteRun( + java( + """ + import java.util.Arrays; + import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void test() { + List listA = Arrays.asList("a", "b", "c"); + assertThat(listA).isNotNull(); + int x=3; + assertThat(listA).hasSize(x); + } + private int[] notification() { + return new int[]{1, 2, 3}; + } + } + """ + ) + ); + } + + @Test + void ignoreIncorrectUseOfExtracting() { + //language=java + rewriteRun( + java( + """ + import static org.assertj.core.api.Assertions.assertThat; + + class Node { Node parent; Node getParent() { return parent; } } + + class MyTest { + // Should not collapse these two, even if `extracting` is used incorrectly + void b(Node node) { + assertThat(node).extracting(Node::getParent); + assertThat(node).isNotNull(); + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/assertj/IsEqualToEmptyStringTest.java b/src/test/java/org/openrewrite/java/testing/assertj/IsEqualToEmptyStringTest.java deleted file mode 100644 index 9edf572d6..000000000 --- a/src/test/java/org/openrewrite/java/testing/assertj/IsEqualToEmptyStringTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2024 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.assertj; - -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 IsEqualToEmptyStringTest implements RewriteTest { - - @Override - public void defaults(RecipeSpec spec) { - spec - .parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "assertj-core-3.24")) - .recipe(new IsEqualToEmptyString()); - } - - @DocumentExample - @Test - void convertsIsEqualToEmptyString() { - rewriteRun( - //language=java - java( - """ - import static org.assertj.core.api.Assertions.assertThat; - class Test { - void test() { - assertThat("test").isEqualTo(""); - } - } - """, - """ - import static org.assertj.core.api.Assertions.assertThat; - class Test { - void test() { - assertThat("test").isEmpty(); - } - } - """ - ) - ); - } -} diff --git a/src/test/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThatTest.java b/src/test/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThatTest.java new file mode 100644 index 000000000..10579e3c2 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/assertj/JUnitAssertInstanceOfToAssertThatTest.java @@ -0,0 +1,232 @@ +/* + * Copyright 2024 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.assertj; + +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 JUnitAssertInstanceOfToAssertThatTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec + .parser(JavaParser.fromJavaVersion() + .classpathFromResources(new InMemoryExecutionContext(), "junit-jupiter-api-5.9")) + .recipe(new JUnitAssertInstanceOfToAssertThat()); + } + + @Test + @DocumentExample + void convertsIsInstanceOf() { + rewriteRun( + // language=java + java( + """ + import static org.junit.jupiter.api.Assertions.assertInstanceOf; + + class Test { + void test() { + assertInstanceOf(Integer.class, 4); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + + class Test { + void test() { + assertThat(4).isInstanceOf(Integer.class); + } + } + """ + ) + ); + } + + @Test + void convertsIsInstanceOfWithMessage() { + rewriteRun( + // language=java + java( + """ + import static org.junit.jupiter.api.Assertions.assertInstanceOf; + + class Test { + void test() { + assertInstanceOf(Integer.class, 4, "error message"); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + + class Test { + void test() { + assertThat(4).as("error message").isInstanceOf(Integer.class); + } + } + """ + ) + ); + } + + @Test + void convertsIsInstanceOfWithMessageLambda() { + rewriteRun( + // language=java + java( + """ + import static org.junit.jupiter.api.Assertions.assertInstanceOf; + + class Test { + void test() { + assertInstanceOf(Integer.class, 4, () -> "error message"); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + + class Test { + void test() { + assertThat(4).as(() -> "error message").isInstanceOf(Integer.class); + } + } + """ + ) + ); + } + + @Test + void convertsIsInstanceOfWithMessageMethodReference() { + rewriteRun( + // language=java + java( + """ + import static org.junit.jupiter.api.Assertions.assertInstanceOf; + + class Test { + void test() { + assertInstanceOf(Integer.class, 4, this::getErrorMessage); + } + + String getErrorMessage() { + return "error message"; + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + + class Test { + void test() { + assertThat(4).as(this::getErrorMessage).isInstanceOf(Integer.class); + } + + String getErrorMessage() { + return "error message"; + } + } + """ + ) + ); + } + + @Test + void canBeRerun() { + rewriteRun( + spec -> spec.parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "assertj-core-3-*")), + // language=java + java( + """ + import static org.assertj.core.api.Assertions.assertThat; + + class Test { + void test() { + assertThat(4).isInstanceOf(Integer.class); + } + } + """ + ) + ); + } + + @Test + void doesNotConvertAnyOtherMethods() { + rewriteRun( + // language=java + java( + """ + import static org.junit.jupiter.api.Assertions.assertInstanceOf; + import static org.junit.jupiter.api.Assertions.assertTrue; + + class Test { + void test() { + assertInstanceOf(Integer.class, 4); + assertTrue(1 == 1, "Message"); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + import static org.junit.jupiter.api.Assertions.assertTrue; + + class Test { + void test() { + assertThat(4).isInstanceOf(Integer.class); + assertTrue(1 == 1, "Message"); + } + } + """ + ) + ); + } + + @Test + void doesConvertNestedMethodInvocations() { + rewriteRun( + // language=java + java( + """ + import static org.junit.jupiter.api.Assertions.assertInstanceOf; + import static org.junit.jupiter.api.Assertions.assertAll; + + class Test { + void test() { + assertAll(() -> assertInstanceOf(Integer.class, 4)); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + import static org.junit.jupiter.api.Assertions.assertAll; + + class Test { + void test() { + assertAll(() -> assertThat(4).isInstanceOf(Integer.class)); + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/assertj/JUnitAssertThrowsToAssertExceptionTypeTest.java b/src/test/java/org/openrewrite/java/testing/assertj/JUnitAssertThrowsToAssertExceptionTypeTest.java index d9651278b..85b9f6a01 100644 --- a/src/test/java/org/openrewrite/java/testing/assertj/JUnitAssertThrowsToAssertExceptionTypeTest.java +++ b/src/test/java/org/openrewrite/java/testing/assertj/JUnitAssertThrowsToAssertExceptionTypeTest.java @@ -35,6 +35,7 @@ public void defaults(RecipeSpec spec) { .recipe(new JUnitAssertThrowsToAssertExceptionType()); } + @SuppressWarnings({"Convert2MethodRef", "CodeBlock2Expr"}) @DocumentExample @Test void toAssertExceptionOfType() { @@ -43,23 +44,25 @@ void toAssertExceptionOfType() { java( """ import static org.junit.jupiter.api.Assertions.assertThrows; - + public class SimpleExpectedExceptionTest { public void throwsExceptionWithSpecificType() { - assertThrows(NullPointerException.class, () -> { - throw new NullPointerException(); - }); + assertThrows(NullPointerException.class, () -> foo()); + } + void foo() { + throw new NullPointerException(); } } """, """ import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; - + public class SimpleExpectedExceptionTest { public void throwsExceptionWithSpecificType() { - assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> { - throw new NullPointerException(); - }); + assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> foo()); + } + void foo() { + throw new NullPointerException(); } } """ @@ -127,6 +130,7 @@ public void throwsExceptionWithSpecificType() { * A degenerate case showing we need to make sure the assertThrows appears * immediately inside a J.Block. */ + @SuppressWarnings("ThrowableNotThrown") @Test @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/pull/331") void assertThrowsTernaryAssignment() { diff --git a/src/test/java/org/openrewrite/java/testing/assertj/SimplifyAssertJAssertionTest.java b/src/test/java/org/openrewrite/java/testing/assertj/SimplifyAssertJAssertionTest.java new file mode 100644 index 000000000..32ddb3b93 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/assertj/SimplifyAssertJAssertionTest.java @@ -0,0 +1,166 @@ +/* + * Copyright 2023 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.assertj; + +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 SimplifyAssertJAssertionTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "assertj-core-3.24")); + } + + @Test + void primitiveBooleanIsFalse() { + rewriteRun( + spec -> spec.recipe(new SimplifyAssertJAssertion("isEqualTo", "false", "isFalse", "boolean")), + //language=java + java( + """ + import static org.assertj.core.api.Assertions.assertThat; + class Test { + void test() { + assertThat(false).isEqualTo(false); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + class Test { + void test() { + assertThat(false).isFalse(); + } + } + """ + ) + ); + } + + @Test + void objectBooleanIsFalse() { + rewriteRun( + spec -> spec.recipe(new SimplifyAssertJAssertion("isEqualTo", "false", "isFalse", "java.lang.Boolean")), + //language=java + java( + """ + import static org.assertj.core.api.Assertions.assertThat; + class Test { + void test(Boolean arg) { + assertThat(arg).isEqualTo(false); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + class Test { + void test(Boolean arg) { + assertThat(arg).isFalse(); + } + } + """ + ) + ); + } + + + @DocumentExample + @Test + void convertsStringIsEqualToNull() { + rewriteRun( + spec -> spec.recipe(new SimplifyAssertJAssertion("isEqualTo", "null", "isNull", "java.lang.Object")), + // language=java + java( + """ + import static org.assertj.core.api.Assertions.assertThat; + class Test { + void test(String a) { + assertThat(a).isEqualTo(null); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + class Test { + void test(String a) { + assertThat(a).isNull(); + } + } + """ + ) + ); + } + + @Test + void convertsObjectIsEqualToNull() { + rewriteRun( + spec -> spec.recipe(new SimplifyAssertJAssertion("isEqualTo", "null", "isNull", "java.lang.Object")), + // language=java + java( + """ + import static org.assertj.core.api.Assertions.assertThat; + class Test { + void test(Object o) { + assertThat(o).isEqualTo(null); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + class Test { + void test(Object o) { + assertThat(o).isNull(); + } + } + """ + ) + ); + } + + + @Test + void convertsIsEqualToEmptyString() { + rewriteRun( + spec -> spec.recipe(new SimplifyAssertJAssertion("isEqualTo", "\"\"", "isEmpty", "java.lang.String")), + // language=java + java( + """ + import static org.assertj.core.api.Assertions.assertThat; + class Test { + void test() { + assertThat("test").isEqualTo(""); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + class Test { + void test() { + assertThat("test").isEmpty(); + } + } + """ + ) + ); + } + +} diff --git a/src/test/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertionTest.java b/src/test/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertionTest.java index f50cd45f5..b88b7d315 100644 --- a/src/test/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertionTest.java +++ b/src/test/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertionTest.java @@ -16,10 +16,10 @@ package org.openrewrite.java.testing.assertj; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.InMemoryExecutionContext; -import org.openrewrite.Issue; import org.openrewrite.java.JavaParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; @@ -30,8 +30,7 @@ class SimplifyChainedAssertJAssertionTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { spec - .parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), - "junit-jupiter-api-5.9", "assertj-core-3.24")); + .parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "assertj-core-3.24")); } @DocumentExample @@ -42,24 +41,18 @@ void stringIsEmpty() { //language=java java( """ - import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; class MyTest { - @Test void testMethod() { assertThat("hello world".isEmpty()).isTrue(); } } """, """ - import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; class MyTest { - @Test void testMethod() { assertThat("hello world").isEmpty(); } @@ -76,7 +69,7 @@ void stringIsEmptyDescribedAs() { spec -> spec.recipe(new SimplifyChainedAssertJAssertion("isEmpty", "isTrue", "isEmpty", "java.lang.String")), //language=java java( - """ + """ import static org.assertj.core.api.Assertions.assertThat; class MyTest { @@ -85,7 +78,7 @@ void testMethod(String actual) { } } """, - """ + """ import static org.assertj.core.api.Assertions.assertThat; class MyTest { @@ -108,32 +101,26 @@ void chainedRecipes() { //language=java java( """ - import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void testMethod() { assertThat(getString().isEmpty()).isTrue(); } - + String getString() { return "hello world"; } } """, """ - import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void testMethod() { assertThat(getString()).isEmpty(); } - + String getString() { return "hello world"; } @@ -153,38 +140,30 @@ void chainedRecipesOfDifferingTypes() { //language=java java( """ - import org.junit.jupiter.api.Test; - import java.nio.file.Path; - + import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void string(String actual) { assertThat(actual.startsWith("prefix")).isTrue(); } - - @Test + void path(Path actual) { assertThat(actual.startsWith("prefix")).isTrue(); } } """, """ - import org.junit.jupiter.api.Test; - import java.nio.file.Path; - + import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void string(String actual) { assertThat(actual).startsWith("prefix"); } - - @Test + void path(Path actual) { assertThat(actual).startsWithRaw(Path.of("prefix")); } @@ -201,34 +180,28 @@ void assertThatArgHasArgument() { //language=java java( """ - import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void testMethod() { String expected = "hello world"; assertThat(getString().equalsIgnoreCase(expected)).isTrue(); } - + String getString() { return "hello world"; } } """, """ - import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void testMethod() { String expected = "hello world"; assertThat(getString()).isEqualToIgnoringCase(expected); } - + String getString() { return "hello world"; } @@ -245,34 +218,28 @@ void replacementHasArgument() { //language=java java( """ - import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void testMethod() { int length = 5; assertThat(getString().length()).isEqualTo(length); } - + String getString() { return "hello world"; } } """, """ - import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void testMethod() { int length = 5; assertThat(getString()).hasSize(length); } - + String getString() { return "hello world"; } @@ -290,32 +257,26 @@ void normalCase() { //language=java java( """ - import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void testMethod() { assertThat(getString().trim()).isEmpty(); } - + String getString() { return "hello world"; } } """, """ - import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void testMethod() { assertThat(getString()).isBlank(); } - + String getString() { return "hello world"; } @@ -335,12 +296,9 @@ void stringContains() { //language=java java( """ - import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; class MyTest { - @Test void testMethod() { assertThat("hello world".contains("lo wo")).isTrue(); assertThat("hello world".contains("lll")).isFalse(); @@ -348,12 +306,9 @@ void testMethod() { } """, """ - import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; class MyTest { - @Test void testMethod() { assertThat("hello world").contains("lo wo"); assertThat("hello world").doesNotContain("lll"); @@ -364,6 +319,48 @@ void testMethod() { ); } + @Test + void stringContainsObjectMethod() { + rewriteRun( + spec -> spec.recipes( + new SimplifyChainedAssertJAssertion("contains", "isTrue", "contains", "java.lang.String")), + //language=java + java( + """ + import static org.assertj.core.api.Assertions.assertThat; + + class Pojo { + public String getString() { + return "lo wo"; + } + } + + class MyTest { + void testMethod() { + var pojo = new Pojo(); + assertThat("hello world".contains(pojo.getString())).isTrue(); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + + class Pojo { + public String getString() { + return "lo wo"; + } + } + + class MyTest { + void testMethod() { + var pojo = new Pojo(); + assertThat("hello world").contains(pojo.getString()); + } + } + """ + ) + ); + } @Test void mapMethodDealsWithTwoArguments() { @@ -372,40 +369,36 @@ void mapMethodDealsWithTwoArguments() { //language=java java( """ - import org.junit.jupiter.api.Test; import java.util.Collections; import java.util.Map; - + import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void testMethod() { String key = "key"; String value = "value"; assertThat(getMap().get(key)).isEqualTo(value); } - + Map getMap() { return Collections.emptyMap(); } } """, """ - import org.junit.jupiter.api.Test; import java.util.Collections; import java.util.Map; - + import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void testMethod() { String key = "key"; String value = "value"; assertThat(getMap()).containsEntry(key, value); } - + Map getMap() { return Collections.emptyMap(); } @@ -415,6 +408,28 @@ Map getMap() { ); } + @Test + void keySetContainsWithMultipleArguments() { + rewriteRun( + spec -> spec.recipe(new SimplifyChainedAssertJAssertion("keySet", "contains", "containsKey", "java.util.Map")), + //language=java + java( + """ + import java.util.Map; + + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void testMethod(Map map) { + // we don't yet support `containsKeys` + assertThat(map.keySet()).contains("a", "b", "c"); + } + } + """ + ) + ); + } + @Test void isNotEmptyTest() { rewriteRun( @@ -422,32 +437,26 @@ void isNotEmptyTest() { //language=java java( """ - import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void testMethod() { assertThat(getString().isEmpty()).isFalse(); } - + String getString() { return "hello world"; } } """, """ - import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void testMethod() { assertThat(getString()).isNotEmpty(); } - + String getString() { return "hello world"; } @@ -464,16 +473,13 @@ void doesNoRunOnWrongCombination() { //language=java java( """ - import org.junit.jupiter.api.Test; - import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void testMethod() { assertThat(getString().isBlank()).isFalse(); } - + String getString() { return "hello world"; } @@ -483,42 +489,83 @@ String getString() { ); } - @Test - @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/398") - void sizeIsEqualToZeroToIsEmpty() { - rewriteRun( - spec -> spec.recipe(new SimplifyChainedAssertJAssertion("size", "isEqualTo", "hasSize", "java.util.List")), - //language=java - java( - """ - import org.junit.jupiter.api.Test; - import java.util.List; - - import static org.assertj.core.api.Assertions.assertThat; - - class MyTest { - @Test - void testMethod() { - List objectIdentifies = List.of(); - assertThat(objectIdentifies.size()).isEqualTo(0); - } - } - """, - """ - import org.junit.jupiter.api.Test; - import java.util.List; - - import static org.assertj.core.api.Assertions.assertThat; - - class MyTest { - @Test - void testMethod() { - List objectIdentifies = List.of(); - assertThat(objectIdentifies).isEmpty(); - } - } - """ - ) - ); + @Nested + class OptionalAssertions { + + @Test + void simplifyPresenceAssertion() { + rewriteRun( + spec -> spec.recipes( + new SimplifyChainedAssertJAssertion("isPresent", "isTrue", "isPresent", "java.util.Optional"), + new SimplifyChainedAssertJAssertion("isEmpty", "isTrue", "isEmpty", "java.util.Optional"), + new SimplifyChainedAssertJAssertion("isPresent", "isFalse", "isNotPresent", "java.util.Optional"), + new SimplifyChainedAssertJAssertion("isEmpty", "isFalse", "isNotEmpty", "java.util.Optional") + ), + //language=java + java( + """ + import static org.assertj.core.api.Assertions.assertThat; + import java.util.Optional; + + class Test { + void simpleTest(Optional o) { + assertThat(o.isPresent()).isTrue(); + assertThat(o.isEmpty()).isTrue(); + assertThat(o.isPresent()).isFalse(); + assertThat(o.isEmpty()).isFalse(); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + import java.util.Optional; + + class Test { + void simpleTest(Optional o) { + assertThat(o).isPresent(); + assertThat(o).isEmpty(); + assertThat(o).isNotPresent(); + assertThat(o).isNotEmpty(); + } + } + """ + ) + ); + } + + @Test + void simplifiyEqualityAssertion() { + rewriteRun( + spec -> spec.recipes( + new SimplifyChainedAssertJAssertion("get", "isEqualTo", "contains", "java.util.Optional"), + new SimplifyChainedAssertJAssertion("get", "isSameAs", "containsSame", "java.util.Optional") + ), + //language=java + java( + """ + import static org.assertj.core.api.Assertions.assertThat; + import java.util.Optional; + + class Test { + void simpleTest(Optional o) { + assertThat(o.get()).isEqualTo("foo"); + assertThat(o.get()).isSameAs("foo"); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + import java.util.Optional; + + class Test { + void simpleTest(Optional o) { + assertThat(o).contains("foo"); + assertThat(o).containsSame("foo"); + } + } + """ + ) + ); + } } } diff --git a/src/test/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertionWithOptionalTest.java b/src/test/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertionWithOptionalTest.java deleted file mode 100644 index 8b515a371..000000000 --- a/src/test/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertionWithOptionalTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2024 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.assertj; - -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 SimplifyChainedAssertJAssertionWithOptionalTest implements RewriteTest { - @Override - public void defaults(RecipeSpec spec) { - spec - .parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), - "junit-jupiter-api-5.9", "assertj-core-3.24")); - } - - - @DocumentExample - @Test - void simplifyPresenceAssertion() { - rewriteRun( - spec -> spec.recipes( - new SimplifyChainedAssertJAssertion("isPresent", "isTrue", "isPresent", "java.util.Optional"), - new SimplifyChainedAssertJAssertion("isEmpty", "isTrue", "isEmpty", "java.util.Optional"), - new SimplifyChainedAssertJAssertion("isPresent", "isFalse", "isNotPresent", "java.util.Optional"), - new SimplifyChainedAssertJAssertion("isEmpty", "isFalse", "isNotEmpty", "java.util.Optional") - ), - //language=java - java( - """ - import static org.assertj.core.api.Assertions.assertThat; - import java.util.Optional; - import org.junit.jupiter.api.Test; - - class Test { - - @Test - void simpleTest() { - Optional o = Optional.empty(); - assertThat(o.isPresent()).isTrue(); - assertThat(o.isEmpty()).isTrue(); - assertThat(o.isPresent()).isFalse(); - assertThat(o.isEmpty()).isFalse(); - } - } - """, - """ - import static org.assertj.core.api.Assertions.assertThat; - import java.util.Optional; - import org.junit.jupiter.api.Test; - - class Test { - - @Test - void simpleTest() { - Optional o = Optional.empty(); - assertThat(o).isPresent(); - assertThat(o).isEmpty(); - assertThat(o).isNotPresent(); - assertThat(o).isNotEmpty(); - } - } - """ - ) - ); - } - - @Test - void simplifiyEqualityAssertion() { - rewriteRun( - spec -> spec.recipes( - new SimplifyChainedAssertJAssertion("get", "isEqualTo", "contains", "java.util.Optional"), - new SimplifyChainedAssertJAssertion("get", "isSameAs", "containsSame", "java.util.Optional") - ), - //language=java - java( - """ - import static org.assertj.core.api.Assertions.assertThat; - import java.util.Optional; - import org.junit.jupiter.api.Test; - - class Test { - - @Test - void simpleTest() { - Optional o = Optional.empty(); - assertThat(o.get()).isEqualTo("foo"); - assertThat(o.get()).isSameAs("foo"); - } - } - """, - """ - import static org.assertj.core.api.Assertions.assertThat; - import java.util.Optional; - import org.junit.jupiter.api.Test; - - class Test { - - @Test - void simpleTest() { - Optional o = Optional.empty(); - assertThat(o).contains("foo"); - assertThat(o).containsSame("foo"); - } - } - """ - ) - ); - } - -} diff --git a/src/test/java/org/openrewrite/java/testing/assertj/MigrateChainedAssertToAssertJTest.java b/src/test/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertionsTest.java similarity index 82% rename from src/test/java/org/openrewrite/java/testing/assertj/MigrateChainedAssertToAssertJTest.java rename to src/test/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertionsTest.java index 0f54f4f47..cc5bed354 100644 --- a/src/test/java/org/openrewrite/java/testing/assertj/MigrateChainedAssertToAssertJTest.java +++ b/src/test/java/org/openrewrite/java/testing/assertj/SimplifyChainedAssertJAssertionsTest.java @@ -34,14 +34,12 @@ import static org.openrewrite.java.Assertions.java; -class MigrateChainedAssertToAssertJTest implements RewriteTest { +class SimplifyChainedAssertJAssertionsTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { spec .parser(JavaParser.fromJavaVersion() - .classpathFromResources(new InMemoryExecutionContext(), - "junit-jupiter-api-5.9", - "assertj-core-3.24")) + .classpathFromResources(new InMemoryExecutionContext(), "assertj-core-3.24")) .recipe(Environment.builder() .scanRuntimeClasspath("org.openrewrite.java.testing.assertj") .build() @@ -56,31 +54,27 @@ void stringIsEmptyExample() { rewriteRun( //language=java java( - """ - import org.junit.jupiter.api.Test; - - import static org.assertj.core.api.Assertions.assertThat; - - class MyTest { - @Test - void testMethod() { - String s = "hello world"; - assertThat(s.isEmpty()).isTrue(); - } - } - """, """ - import org.junit.jupiter.api.Test; - - import static org.assertj.core.api.Assertions.assertThat; - - class MyTest { - @Test - void testMethod() { - String s = "hello world"; - assertThat(s).isEmpty(); - } - } - """) + """ + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void testMethod() { + String s = "hello world"; + assertThat(s.isEmpty()).isTrue(); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void testMethod() { + String s = "hello world"; + assertThat(s).isEmpty(); + } + } + """ + ) ); } @@ -91,8 +85,11 @@ private static Stream stringReplacements() { Arguments.arguments("equalsIgnoreCase", "isTrue", "isEqualToIgnoringCase", "expected", ""), Arguments.arguments("contains", "isTrue", "contains", "expected", ""), Arguments.arguments("startsWith", "isTrue", "startsWith", "expected", ""), + Arguments.arguments("startsWith", "isFalse", "doesNotStartWith", "expected", ""), Arguments.arguments("endsWith", "isTrue", "endsWith", "expected", ""), + Arguments.arguments("endsWith", "isFalse", "doesNotEndWith", "expected", ""), Arguments.arguments("matches", "isTrue", "matches", "expected", ""), + Arguments.arguments("matches", "isFalse", "doesNotMatch", "expected", ""), Arguments.arguments("trim", "isEmpty", "isBlank", "", ""), Arguments.arguments("length", "isEqualTo", "hasSize", "", "length"), Arguments.arguments("isEmpty", "isFalse", "isNotEmpty", "", "") @@ -104,12 +101,10 @@ private static Stream stringReplacements() { void stringReplacements(String chainedAssertion, String assertToReplace, String dedicatedAssertion, String firstArg, String secondArg) { //language=java String template = """ - import org.junit.jupiter.api.Test; - + import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void test() { int length = 5; String expected = "hello world"; @@ -159,13 +154,11 @@ private static Stream fileReplacements() { void fileReplacements(String chainedAssertion, String assertToReplace, String dedicatedAssertion, String firstArg, String secondArg) { //language=java String template = """ - import org.junit.jupiter.api.Test; import java.io.File; - + import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void test() { int length = 5; String name = "hello world"; @@ -207,14 +200,12 @@ private static Stream pathReplacements() { void pathReplacements(String chainedAssertion, String assertToReplace, String dedicatedAssertion, String firstArg, String secondArg) { //language=java String template = """ - import org.junit.jupiter.api.Test; import java.nio.file.Path; import java.nio.file.Paths; - + import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void test() { Path path = Paths.get(""); %s @@ -258,9 +249,9 @@ void collectionReplacements(String chainedAssertion, String assertToReplace, Str //language=java String template = """ import java.util.Collection; - + import static org.assertj.core.api.Assertions.assertThat; - + class A { void test(Collection collection, Collection otherCollection) { String something = ""; @@ -300,14 +291,12 @@ private static Stream mapReplacements() { void mapReplacements(String chainedAssertion, String assertToReplace, String dedicatedAssertion, String firstArg, String secondArg) { //language=java String template = """ - import org.junit.jupiter.api.Test; import java.util.Collections; import java.util.Map; - + import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void test() { Map otherMap = Collections.emptyMap(); String key = "key"; @@ -341,13 +330,11 @@ void mapHasSameSizeAs() { //language=java java( """ - import org.junit.jupiter.api.Test; import java.util.Map; - + import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void testMethod() { Map mapA = Map.of(); Map mapB = Map.of(); @@ -356,13 +343,11 @@ void testMethod() { } """, """ - import org.junit.jupiter.api.Test; import java.util.Map; - + import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void testMethod() { Map mapA = Map.of(); Map mapB = Map.of(); @@ -390,13 +375,11 @@ private static Stream optionalReplacements() { void optionalReplacements(String chainedAssertion, String assertToReplace, String dedicatedAssertion, String arg) { //language=java String template = """ - import org.junit.jupiter.api.Test; import java.util.Optional; - + import static org.assertj.core.api.Assertions.assertThat; - + class MyTest { - @Test void test() { String something = "hello world"; Optional helloWorld = Optional.of("hello world"); @@ -414,4 +397,75 @@ void test() { rewriteRun(java(before, after)); } } + + @Nested + class Iterators { + private static Stream collectionReplacements() { + return Stream.of( + Arguments.arguments("hasNext", "isTrue", "hasNext"), + Arguments.arguments("hasNext", "isFalse", "isExhausted") + ); + } + + @ParameterizedTest + @MethodSource("collectionReplacements") + void collectionReplacements(String chainedAssertion, String assertToReplace, String dedicatedAssertion) { + //language=java + String template = """ + import java.util.Iterator; + + import static org.assertj.core.api.Assertions.assertThat; + + class A { + void test(Iterator iterator, Iterator otherIterator) { + String something = ""; + %s + } + } + """; + String assertBefore = "assertThat(iterator.%s()).%s();"; + String assertAfter = "assertThat(iterator).%s();"; + + String formattedAssertBefore = assertBefore.formatted(chainedAssertion, assertToReplace); + + String before = String.format(template, formattedAssertBefore); + String after = String.format(template, assertAfter.formatted(dedicatedAssertion)); + + rewriteRun( + java(before, after) + ); + } + } + + @Nested + class Objects { + @Test + void objectoToStringReplacement() { + rewriteRun( + //language=java + java( + """ + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void testMethod(Object argument) { + String s = "hello world"; + assertThat(argument.toString()).isEqualTo("value"); + } + } + """, + """ + import static org.assertj.core.api.Assertions.assertThat; + + class MyTest { + void testMethod(Object argument) { + String s = "hello world"; + assertThat(argument).hasToString("value"); + } + } + """ + ) + ); + } + } } diff --git a/src/test/java/org/openrewrite/java/testing/assertj/StaticImportsTest.java b/src/test/java/org/openrewrite/java/testing/assertj/StaticImportsTest.java index b23b8e13a..3b6808446 100644 --- a/src/test/java/org/openrewrite/java/testing/assertj/StaticImportsTest.java +++ b/src/test/java/org/openrewrite/java/testing/assertj/StaticImportsTest.java @@ -51,6 +51,7 @@ void useAssertionsStaticImport() { import org.assertj.core.api.AssertionsForInterfaceTypes; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + import static org.assertj.core.api.Fail.fail; public class Test { List exampleList; @@ -59,6 +60,7 @@ void method() { AssertionsForClassTypes.assertThat(true).isTrue(); assertThat(true).isTrue(); assertThat(exampleList).hasSize(0); + fail("This is a failure"); } } """, @@ -66,6 +68,7 @@ void method() { import java.util.List; import static org.assertj.core.api.Assertions.assertThat; + import static org.assertj.core.api.Assertions.fail; public class Test { List exampleList; @@ -74,6 +77,7 @@ void method() { assertThat(true).isTrue(); assertThat(true).isTrue(); assertThat(exampleList).hasSize(0); + fail("This is a failure"); } } """ diff --git a/src/test/java/org/openrewrite/java/testing/cleanup/AssertEqualsBooleanToAssertBooleanTest.java b/src/test/java/org/openrewrite/java/testing/cleanup/AssertEqualsBooleanToAssertBooleanTest.java index 4d39ff54e..13bbaf4ae 100644 --- a/src/test/java/org/openrewrite/java/testing/cleanup/AssertEqualsBooleanToAssertBooleanTest.java +++ b/src/test/java/org/openrewrite/java/testing/cleanup/AssertEqualsBooleanToAssertBooleanTest.java @@ -105,4 +105,23 @@ void test() { ) ); } + + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/587") + void assertTrueWithNonBoolean() { + rewriteRun( + spec -> spec.recipe(new AssertEqualsBooleanToAssertBoolean()), + // language=java + java( + """ + import static org.junit.jupiter.api.Assertions.assertEquals; + class Main { + void foo() { + assertEquals(true, new Object()); + } + } + """ + ) + ); + } } diff --git a/src/test/java/org/openrewrite/java/testing/cleanup/AssertLiteralBooleanToFailTest.java b/src/test/java/org/openrewrite/java/testing/cleanup/AssertLiteralBooleanToFailTest.java new file mode 100644 index 000000000..da773f4b1 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/cleanup/AssertLiteralBooleanToFailTest.java @@ -0,0 +1,117 @@ +/* + * Copyright 2024 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.cleanup; + +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 AssertLiteralBooleanToFailTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec + .parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "junit-jupiter-api-5.9")) + .recipe(new AssertLiteralBooleanToFailRecipe()); + } + + @Test + @DocumentExample + @SuppressWarnings("SimplifiableAssertion") + void assertWithStaticImports() { + //language=java + rewriteRun( + java( + """ + import static org.junit.jupiter.api.Assertions.assertFalse; + import static org.junit.jupiter.api.Assertions.assertTrue; + + public class Test { + void test() { + assertFalse(true, "assert false true"); + assertTrue(false, "assert true false"); + } + } + """, + """ + import org.junit.jupiter.api.Assertions; + + public class Test { + void test() { + Assertions.fail("assert false true"); + Assertions.fail("assert true false"); + } + } + """ + ) + ); + } + + @Test + @SuppressWarnings("SimplifiableAssertion") + void assertWithAssertionsImport() { + //language=java + rewriteRun( + java( + """ + import org.junit.jupiter.api.Assertions; + + public class Test { + void test() { + Assertions.assertFalse(true, "assert false true"); + Assertions.assertTrue(false, "assert true false"); + } + } + """, + """ + import org.junit.jupiter.api.Assertions; + + public class Test { + void test() { + Assertions.fail("assert false true"); + Assertions.fail("assert true false"); + } + } + """ + ) + ); + } + + @Test + void noChangeWhenNotLiteral() { + //language=java + rewriteRun( + java( + """ + import static org.junit.jupiter.api.Assertions.assertFalse; + import static org.junit.jupiter.api.Assertions.assertTrue; + + public class Test { + void test(boolean a, Object b) { + assertTrue(a, "message"); + assertFalse(b.equals("foo"), "message"); + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/cleanup/AssertionsArgumentOrderTest.java b/src/test/java/org/openrewrite/java/testing/cleanup/AssertionsArgumentOrderTest.java index 80e79f1d6..962faa04e 100644 --- a/src/test/java/org/openrewrite/java/testing/cleanup/AssertionsArgumentOrderTest.java +++ b/src/test/java/org/openrewrite/java/testing/cleanup/AssertionsArgumentOrderTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Issue; import org.openrewrite.java.JavaParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; @@ -29,7 +30,7 @@ class AssertionsArgumentOrderTest implements RewriteTest { public void defaults(RecipeSpec spec) { spec.recipe(new AssertionsArgumentOrder()) .parser(JavaParser.fromJavaVersion() - .classpathFromResources(new InMemoryExecutionContext(), "junit-jupiter-api-5.9", "testng-7.7")); + .classpathFromResources(new InMemoryExecutionContext(), "junit-jupiter-api-5.9", "testng-7.7", "junit-4.13")); } @DocumentExample @@ -40,7 +41,7 @@ void junitAssertEqualsHavingPrimitiveArg() { java( """ import static org.junit.jupiter.api.Assertions.assertEquals; - + class MyTest { void someMethod() { assertEquals(result(), "result"); @@ -54,7 +55,7 @@ String result() { """, """ import static org.junit.jupiter.api.Assertions.assertEquals; - + class MyTest { void someMethod() { assertEquals("result", result()); @@ -71,14 +72,98 @@ String result() { } @Test - void junitAssertNullAndAssertNotNull() { + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/636") + void junit4AssertEqualsHavingStringArg() { + rewriteRun( + //language=java + java( + """ + import static org.junit.Assert.assertEquals; + + class MyTest { + void someMethod() { + assertEquals(result(), "result"); + assertEquals("result", result()); + assertEquals("message", result(), "result"); + assertEquals("message", "result", result()); + } + String result() { + return "result"; + } + } + """, + """ + import static org.junit.Assert.assertEquals; + + class MyTest { + void someMethod() { + assertEquals("result", result()); + assertEquals("result", result()); + assertEquals("message", "result", result()); + assertEquals("message", "result", result()); + } + String result() { + return "result"; + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/636") + void junit4AssertEqualsHavingPrimitiveArg() { + rewriteRun( + //language=java + java( + """ + import static org.junit.Assert.assertEquals; + + class MyTest { + void someMethod() { + assertEquals(0L, 1L); + assertEquals(1L, 0L); + assertEquals(longResult(), 0L); + assertEquals(0L, longResult()); + assertEquals("message", 0L, longResult()); + assertEquals("message", longResult(), 0L); + } + long longResult() { + return 0L; + } + } + """, + """ + import static org.junit.Assert.assertEquals; + + class MyTest { + void someMethod() { + assertEquals(0L, 1L); + assertEquals(1L, 0L); + assertEquals(0L, longResult()); + assertEquals(0L, longResult()); + assertEquals("message", 0L, longResult()); + assertEquals("message", 0L, longResult()); + } + long longResult() { + return 0L; + } + } + """ + ) + ); + } + + @Test + void jupiterAssertNullAndAssertNotNull() { rewriteRun( //language=java java( """ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; - + class MyTest { void someMethod() { assertNull(result(), "message"); @@ -94,7 +179,7 @@ String result() { """ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; - + class MyTest { void someMethod() { assertNull(result(), "message"); @@ -111,6 +196,48 @@ String result() { ); } + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/636") + void junitAssertNullAndAssertNotNull() { + rewriteRun( + //language=java + java( + """ + import static org.junit.Assert.assertNotNull; + import static org.junit.Assert.assertNull; + + class MyTest { + void someMethod() { + assertNull(result(), "message"); + assertNull("message", result()); + assertNotNull(result(), "message"); + assertNotNull("message", result()); + } + String result() { + return "result"; + } + } + """, + """ + import static org.junit.Assert.assertNotNull; + import static org.junit.Assert.assertNull; + + class MyTest { + void someMethod() { + assertNull("message", result()); + assertNull("message", result()); + assertNotNull("message", result()); + assertNotNull("message", result()); + } + String result() { + return "result"; + } + } + """ + ) + ); + } + @Test void jupiterAssertSameNotSame() { rewriteRun( @@ -119,7 +246,7 @@ void jupiterAssertSameNotSame() { """ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertNotSame; - + class MyTest { private static final Integer LIMIT = 0; private static String MESSAGE = ""; @@ -127,7 +254,7 @@ void someMethod() { assertSame(getCount(), LIMIT); assertSame(getCount(), MyTest.LIMIT); assertSame(LIMIT, getCount()); - + assertNotSame(getMsg(), MESSAGE); } String getMsg() { @@ -141,7 +268,61 @@ Integer getCount() { """ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertNotSame; - + + class MyTest { + private static final Integer LIMIT = 0; + private static String MESSAGE = ""; + void someMethod() { + assertSame(LIMIT, getCount()); + assertSame(MyTest.LIMIT, getCount()); + assertSame(LIMIT, getCount()); + + assertNotSame(getMsg(), MESSAGE); + } + String getMsg() { + return ""; + } + Integer getCount() { + return 1; + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/636") + void junit4AssertSameNotSame() { + rewriteRun( + //language=java + java( + """ + import static org.junit.Assert.assertSame; + import static org.junit.Assert.assertNotSame; + + class MyTest { + private static final Integer LIMIT = 0; + private static String MESSAGE = ""; + void someMethod() { + assertSame(getCount(), LIMIT); + assertSame(getCount(), MyTest.LIMIT); + assertSame(LIMIT, getCount()); + + assertNotSame(getMsg(), MESSAGE); + } + String getMsg() { + return ""; + } + Integer getCount() { + return 1; + } + } + """, + """ + import static org.junit.Assert.assertSame; + import static org.junit.Assert.assertNotSame; + class MyTest { private static final Integer LIMIT = 0; private static String MESSAGE = ""; @@ -149,7 +330,7 @@ void someMethod() { assertSame(LIMIT, getCount()); assertSame(MyTest.LIMIT, getCount()); assertSame(LIMIT, getCount()); - + assertNotSame(getMsg(), MESSAGE); } String getMsg() { @@ -171,7 +352,7 @@ void jupiterAssertArrayEquals() { java( """ import static org.junit.jupiter.api.Assertions.assertArrayEquals; - + class MyTest { void someMethod() { assertArrayEquals(result(), new String[]{""}); @@ -184,7 +365,7 @@ String[] result() { """, """ import static org.junit.jupiter.api.Assertions.assertArrayEquals; - + class MyTest { void someMethod() { assertArrayEquals(new String[]{""}, result()); @@ -199,6 +380,46 @@ String[] result() { ); } + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/636") + void junitAssertArrayEquals() { + rewriteRun( + //language=java + java( + """ + import static org.junit.Assert.assertArrayEquals; + + class MyTest { + void someMethod() { + assertArrayEquals(result(), new String[]{""}); + assertArrayEquals(new String[]{""}, result()); + assertArrayEquals("message", new String[]{""}, result()); + assertArrayEquals("message", result(), new String[]{""}); + } + String[] result() { + return null; + } + } + """, + """ + import static org.junit.Assert.assertArrayEquals; + + class MyTest { + void someMethod() { + assertArrayEquals(new String[]{""}, result()); + assertArrayEquals(new String[]{""}, result()); + assertArrayEquals("message", new String[]{""}, result()); + assertArrayEquals("message", new String[]{""}, result()); + } + String[] result() { + return null; + } + } + """ + ) + ); + } + @Test void junitIterableEquals() { rewriteRun( @@ -208,9 +429,9 @@ void junitIterableEquals() { import java.util.ArrayList; import java.util.Collections; import java.util.List; - + import static org.junit.jupiter.api.Assertions.assertIterableEquals; - + class MyTest { static final Iterable COSNT_LIST = new ArrayList<>(); void someTest() { @@ -227,9 +448,9 @@ List doubleList() { import java.util.ArrayList; import java.util.Collections; import java.util.List; - + import static org.junit.jupiter.api.Assertions.assertIterableEquals; - + class MyTest { static final Iterable COSNT_LIST = new ArrayList<>(); void someTest() { @@ -253,7 +474,7 @@ void ngAssertions() { java( """ import static org.testng.Assert.assertEquals; - + class MyTest { void someTest() { assertEquals("abc", someString()); @@ -266,7 +487,7 @@ String someString() { """, """ import static org.testng.Assert.assertEquals; - + class MyTest { void someTest() { assertEquals(someString(), "abc"); diff --git a/src/test/java/org/openrewrite/java/testing/cleanup/RemoveTestPrefixTest.java b/src/test/java/org/openrewrite/java/testing/cleanup/RemoveTestPrefixTest.java index 00be3cd29..aa7e8e238 100644 --- a/src/test/java/org/openrewrite/java/testing/cleanup/RemoveTestPrefixTest.java +++ b/src/test/java/org/openrewrite/java/testing/cleanup/RemoveTestPrefixTest.java @@ -53,7 +53,7 @@ void testMethod() { @Test void test_snake_case() { } - + @Test void testRTFCharacters() { } @@ -85,7 +85,7 @@ void method() { @Test void snake_case() { } - + @Test void rtfCharacters() { } @@ -139,7 +139,7 @@ void ignoreOverriddenMethod() { abstract class AbstractTest { public abstract void testMethod(); } - + class BTest extends AbstractTest { @Test @Override @@ -268,7 +268,7 @@ class ATest { @Test void testMyDoSomethingLogic() { } - + void myDoSomethingLogic() {} } """ @@ -292,7 +292,7 @@ class ATest { @MethodSource void testMyDoSomethingLogic(Arguments args) { } - + static Stream testMyDoSomethingLogic() { return Stream.empty(); } @@ -324,4 +324,57 @@ void tests() { ) ); } + + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/258") + void ignoreWhenStaticImportConflicts() { + rewriteRun( + //language=java + java( + """ + import org.junit.jupiter.api.Test; + import static java.util.List.of; + + class FooTest { + @Test + void testOf() { + of(); + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/258") + void removeOnQualifiedMethodDespiteConflict() { + rewriteRun( + //language=java + java( + """ + import org.junit.jupiter.api.Test; + import java.util.List; + + class FooTest { + @Test + void testOf() { + List.of(); + } + } + """, + """ + import org.junit.jupiter.api.Test; + import java.util.List; + + class FooTest { + @Test + void of() { + List.of(); + } + } + """ + ) + ); + } } diff --git a/src/test/java/org/openrewrite/java/testing/cleanup/TestsShouldIncludeAssertionsTest.java b/src/test/java/org/openrewrite/java/testing/cleanup/TestsShouldIncludeAssertionsTest.java index 1b7c5ce82..aee4f6c03 100644 --- a/src/test/java/org/openrewrite/java/testing/cleanup/TestsShouldIncludeAssertionsTest.java +++ b/src/test/java/org/openrewrite/java/testing/cleanup/TestsShouldIncludeAssertionsTest.java @@ -34,7 +34,7 @@ class TestsShouldIncludeAssertionsTest implements RewriteTest { public void defaults(RecipeSpec spec) { spec .parser(JavaParser.fromJavaVersion() - .classpathFromResources(new InMemoryExecutionContext(), "junit-4.13", "junit-jupiter-api-5.9", "mockito-all-1.10", "hamcrest-2.2", "assertj-core-3.24") + .classpathFromResources(new InMemoryExecutionContext(), "junit-4.13", "junit-jupiter-api-5.9", "mockito-all-1.10", "hamcrest-2.2", "assertj-core-3.24", "spring-test-6.1.12") .dependsOn( List.of( //language=java @@ -70,9 +70,9 @@ public void methodTest() { """, """ import org.junit.jupiter.api.Test; - + import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - + public class AaTest { @Test public void methodTest() { @@ -94,11 +94,11 @@ void hasAssertDoesNotThrowAssertion() { java( """ import org.junit.jupiter.api.Test; - + import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - + public class AaTest { - + @Test public void methodTest() { assertDoesNotThrow(() -> { @@ -119,11 +119,11 @@ void assertJAssertion() { java( """ import org.junit.jupiter.api.Test; - + import static org.assertj.core.api.Assertions.assertThat; - + public class MyTest { - + @Test public void test() { assertThat(notification()).isEqualTo(1); @@ -144,10 +144,10 @@ void hamcrestAssertion() { java( """ import org.junit.jupiter.api.Test; - + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; - + public class ATest { @Test public void methodTest() { @@ -189,16 +189,16 @@ void methodBodyContainsMethodInvocationWithAssert() { """ import java.util.Set; import org.junit.jupiter.api.Test; - + import static org.junit.Assert.assertTrue; - + public class TestClass { @Test public void methodTest() { Set s = Set.of("hello"); testContains(s, "hello"); } - + private static void testContains(Set set, String word) { assertTrue(set.contains(word)); } @@ -218,9 +218,9 @@ void usesAdditionalAssertion() { java( """ package org.foo; - + import java.util.Set; - + public class TestUtil { public static void testContains(Set set, String word) { } @@ -252,7 +252,7 @@ public void changes() { import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import org.junit.jupiter.api.Test; - + public class TestClass { @Test public void doesNotChange() { @@ -287,7 +287,7 @@ void hasMockitoVerify() { class AaTest { @Mock MyMathService myMathService; - + @Test public void verifyTest() { when(myMathService.addIntegers("1", "2")).thenReturn(3); @@ -313,12 +313,12 @@ void hasMockitoVerifyNoInteractions() { class AaTest { @Mock org.learning.math.MyMathService myMathService; - + @Test public void noMore() { verifyNoMoreInteractions(myMathService); } - + @Test public void zero() { verifyZeroInteractions(myMathService); @@ -340,11 +340,11 @@ void hasMockitoDoesNotValidate() { import org.learning.math.MyMathService; import static org.mockito.Mockito.when; import org.learning.math.Stuff; - + class AaTest { @Mock MyMathService myMathService; - + @Test public void methodTest() { when(myMathService.addIntegers("1", "2")).thenReturn(3); @@ -360,11 +360,11 @@ public void methodTest() { import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.Mockito.when; import org.learning.math.Stuff; - + class AaTest { @Mock MyMathService myMathService; - + @Test public void methodTest() { assertDoesNotThrow(() -> { @@ -377,4 +377,64 @@ public void methodTest() { ) ); } + + @Test + void hasMockRestServiceServerVerify() { + //language=java + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + import org.springframework.test.web.client.MockRestServiceServer; + + class AaTest { + private MockRestServiceServer mockServer; + + @Test + public void verifyTest() { + mockServer.verify(); + } + } + """ + ) + ); + } + + @Test + void ignoreDisabled() { + //language=java + rewriteRun( + java( + """ + import org.junit.jupiter.api.Disabled; + import org.junit.jupiter.api.Test; + public class AaTest { + @Disabled + @Test + public void methodTest() { + Integer it = Integer.valueOf("2"); + System.out.println(it); + } + } + """ + ) + ); + } + + @Test + void ignoreEmpty() { + //language=java + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + public class AaTest { + @Test + public void methodTest() { + } + } + """ + ) + ); + } } diff --git a/src/test/java/org/openrewrite/java/testing/cleanup/TestsShouldNotBePublicTest.java b/src/test/java/org/openrewrite/java/testing/cleanup/TestsShouldNotBePublicTest.java index 3d9679549..6e682caac 100644 --- a/src/test/java/org/openrewrite/java/testing/cleanup/TestsShouldNotBePublicTest.java +++ b/src/test/java/org/openrewrite/java/testing/cleanup/TestsShouldNotBePublicTest.java @@ -53,7 +53,7 @@ void testMethod() { @Nested public class NestedTestClass { - + @Test void anotherTestMethod() { } @@ -198,9 +198,9 @@ void ignoreOverriddenMethod() { abstract class AbstractTest { public abstract void testMethod(); } - + class BTest extends AbstractTest { - + @Test @Override public void testMethod() { diff --git a/src/test/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotationUnitTest.java b/src/test/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotationUnitTest.java new file mode 100644 index 000000000..b1820b6fc --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotationUnitTest.java @@ -0,0 +1,233 @@ +/* + * Copyright 2024 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.dbrider; + +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 ExecutionListenerToDbRiderAnnotationUnitTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "spring-test-6.1", "rider-spring-1.18", "rider-junit5-1.44")) + .recipe(new ExecutionListenerToDbRiderAnnotation()); + } + + @Test + @DocumentExample + void replaceAnnotationIfOnlyDbRiderListenerMergedWithDefaults() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners(mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS, listeners = {DBRiderTestExecutionListener.class}) + public class Sample {} + """, + """ + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + public class Sample {} + """ + ) + ); + } + + @Test + void addAnnotationIfOnlyDbRiderListenerReplacedDefaults() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners(mergeMode = TestExecutionListeners.MergeMode.REPLACE_DEFAULTS, listeners = {DBRiderTestExecutionListener.class}) + public class Sample {} + """, + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + @TestExecutionListeners + public class Sample {} + """ + ) + ); + } + + @Test + void addAnnotationIfOnlyDbRiderListenerAndNoMergeModeSpecified() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners(listeners = {DBRiderTestExecutionListener.class}) + public class Sample {} + """, + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + @TestExecutionListeners + public class Sample {} + """ + ) + ); + } + + @Test + void addAnnotationIfOnlyDbRiderListenerThroughValue() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners(DBRiderTestExecutionListener.class) + public class Sample {} + """, + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + @TestExecutionListeners + public class Sample {} + """ + ) + ); + } + + @Test + void addAnnotationIfOnlyDbRiderListenerThroughValueArray() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners({DBRiderTestExecutionListener.class}) + public class Sample {} + """, + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + @TestExecutionListeners + public class Sample {} + """ + ) + ); + } + + @Test + void keepAnnotationIfOnlyDbRiderListenerSetAndNonDefaultSetting() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners(value = {DBRiderTestExecutionListener.class}, inheritListeners = false) + public class Sample {} + """, + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + @TestExecutionListeners(inheritListeners = false) + public class Sample {} + """ + ) + ); + } + + @Test + void removeListenerFromOtherListeners() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import org.springframework.test.context.support.DirtiesContextTestExecutionListener; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners(mergeMode = TestExecutionListeners.MergeMode.REPLACE_DEFAULTS, listeners = {DBRiderTestExecutionListener.class, DirtiesContextTestExecutionListener.class}) + public class Sample {} + """, + """ + import org.springframework.test.context.TestExecutionListeners; + import org.springframework.test.context.support.DirtiesContextTestExecutionListener; + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + @TestExecutionListeners(listeners = {DirtiesContextTestExecutionListener.class}) + public class Sample {} + """ + ) + ); + } + + @Test + void doNotTouchIfNoListenerPresent() { + rewriteRun( + //language=java + java( + """ + @Deprecated + public class Sample {} + """ + ) + ); + } + + @Test + void doNotTouchIfDbRiderAlreadyPresent() { + rewriteRun( + //language=java + java( + """ + import com.github.database.rider.junit5.api.DBRider; + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @DBRider + @TestExecutionListeners(listeners = {DBRiderTestExecutionListener.class}, inheritListeners = false) + public class Sample {} + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/hamcrest/MigrateHamcrestToAssertJTest.java b/src/test/java/org/openrewrite/java/testing/hamcrest/MigrateHamcrestToAssertJTest.java index 64ad82036..b0229562f 100644 --- a/src/test/java/org/openrewrite/java/testing/hamcrest/MigrateHamcrestToAssertJTest.java +++ b/src/test/java/org/openrewrite/java/testing/hamcrest/MigrateHamcrestToAssertJTest.java @@ -21,6 +21,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import org.openrewrite.DocumentExample; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.Issue; @@ -760,4 +761,80 @@ void ba() { ) ); } + + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/538") + void collectionMatchers() { + //language=java + rewriteRun( + java( + """ + import java.util.List; + + import static org.hamcrest.collection.IsCollectionWithSize.hasSize; + import static org.hamcrest.MatcherAssert.assertThat; + + class Foo { + void bar(List list) { + assertThat(list, hasSize(12)); + } + } + """, + """ + import java.util.List; + + import static org.assertj.core.api.Assertions.assertThat; + + class Foo { + void bar(List list) { + assertThat(list).hasSize(12); + } + } + """ + ) + ); + } + + @ParameterizedTest + @ValueSource( + strings = { + "java.util.Date", + "java.time.Instant" + } + ) + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/526") + void greaterThanOrEqualToDate(String type){ + rewriteRun( + java( + """ + import static org.hamcrest.MatcherAssert.assertThat; + import static org.hamcrest.Matchers.greaterThan; + import static org.hamcrest.Matchers.greaterThanOrEqualTo; + import static org.hamcrest.Matchers.lessThan; + import static org.hamcrest.Matchers.lessThanOrEqualTo; + + class Foo { + void bar(%1$s type) { + assertThat(type, lessThan(type)); + assertThat(type, lessThanOrEqualTo(type)); + assertThat(type, greaterThan(type)); + assertThat(type, greaterThanOrEqualTo(type)); + } + } + """.formatted(type), + """ + import static org.assertj.core.api.Assertions.assertThat; + + class Foo { + void bar(%1$s type) { + assertThat(type).isBefore(type); + assertThat(type).isBeforeOrEqualTo(type); + assertThat(type).isAfter(type); + assertThat(type).isAfterOrEqualTo(type); + } + } + """.formatted(type) + ) + ); + } } diff --git a/src/test/java/org/openrewrite/java/testing/jmockit/JMockitMockedVariableToMockitoTest.java b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitAnnotatedArgumentToMockitoTest.java similarity index 54% rename from src/test/java/org/openrewrite/java/testing/jmockit/JMockitMockedVariableToMockitoTest.java rename to src/test/java/org/openrewrite/java/testing/jmockit/JMockitAnnotatedArgumentToMockitoTest.java index 89293f3ef..2cb93ff8f 100644 --- a/src/test/java/org/openrewrite/java/testing/jmockit/JMockitMockedVariableToMockitoTest.java +++ b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitAnnotatedArgumentToMockitoTest.java @@ -17,29 +17,17 @@ 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; +import static org.openrewrite.java.testing.jmockit.JMockitTestUtils.setDefaultParserSettings; + +class JMockitAnnotatedArgumentToMockitoTest implements RewriteTest { -class JMockitMockedVariableToMockitoTest 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", - "mockito-core-3.12", - "mockito-junit-jupiter-3.12" - )) - .recipeFromResource( - "/META-INF/rewrite/jmockit.yml", - "org.openrewrite.java.testing.jmockit.JMockitToMockito" - ); + setDefaultParserSettings(spec); } @DocumentExample @@ -52,7 +40,7 @@ void mockedVariableTest() { import mockit.Mocked; import static org.junit.jupiter.api.Assertions.assertNotNull; - + class A { @Mocked Object mockedObject; @@ -68,7 +56,7 @@ void test(@Mocked Object o, @Mocked Object o2) { import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.assertNotNull; - + class A { @Mock Object mockedObject; @@ -86,7 +74,7 @@ void test() { } @Test - void noVariableTest() { + void mockedNoVariableTest() { rewriteRun( //language=java java( @@ -94,7 +82,7 @@ void noVariableTest() { import mockit.Mocked; import static org.junit.jupiter.api.Assertions.assertNotNull; - + class A { @Mocked Object mockedObject; @@ -108,7 +96,86 @@ void test() { import org.mockito.Mock; import static org.junit.jupiter.api.Assertions.assertNotNull; - + + class A { + @Mock + Object mockedObject; + + void test() { + assertNotNull(mockedObject); + } + } + """ + ) + ); + } + + @Test + void injectableVariableTest() { + //language=java + rewriteRun( + java( + """ + import mockit.Injectable; + + import static org.junit.jupiter.api.Assertions.assertNotNull; + + class A { + @Injectable + Object mockedObject; + + void test(@Injectable Object o, @Injectable Object o2) { + assertNotNull(o); + assertNotNull(o2); + } + } + """, + """ + import org.mockito.Mock; + import org.mockito.Mockito; + + import static org.junit.jupiter.api.Assertions.assertNotNull; + + class A { + @Mock + Object mockedObject; + + void test() { + Object o = Mockito.mock(Object.class); + Object o2 = Mockito.mock(Object.class); + assertNotNull(o); + assertNotNull(o2); + } + } + """ + ) + ); + } + + @Test + void injectableNoVariableTest() { + rewriteRun( + //language=java + java( + """ + import mockit.Injectable; + + import static org.junit.jupiter.api.Assertions.assertNotNull; + + class A { + @Injectable + Object mockedObject; + + void test() { + assertNotNull(mockedObject); + } + } + """, + """ + import org.mockito.Mock; + + import static org.junit.jupiter.api.Assertions.assertNotNull; + class A { @Mock Object mockedObject; diff --git a/src/test/java/org/openrewrite/java/testing/jmockit/JMockitDelegateToMockitoTest.java b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitDelegateToMockitoTest.java new file mode 100644 index 000000000..dca71281e --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitDelegateToMockitoTest.java @@ -0,0 +1,367 @@ +/* + * Copyright 2023 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.jmockit; + +import org.junit.jupiter.api.Disabled; +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 + * with the template being printed out. These tests were written to try to replicate this issue, however I was unable to. + * They may help anyone who wants to add Delegate migration. + */ +@SuppressWarnings("ResultOfMethodCallIgnored") +@Disabled +@Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/522") +class JMockitDelegateToMockitoTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + setDefaultParserSettings(spec); + } + + @DocumentExample + @Test + void whenNoArgsVoidMethod() { + //language=java + rewriteRun( + java( + """ + import mockit.Expectations; + import mockit.Delegate; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + new Expectations() {{ + myObject.wait(); + result = new Delegate() { + public void wait() { + System.out.println("foo"); + } + }; + }}; + myObject.wait(); + } + } + """, + """ + import mockit.Delegate; + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.when; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + doAnswer(invocation -> { + System.out.println("foo"); + return null; + }).when(myObject).wait(); + myObject.wait(); + } + } + """ + ) + ); + } + + @Test + void whenHasArgsVoidMethod() { + //language=java + rewriteRun( + java( + """ + import mockit.Expectations; + import mockit.Delegate; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + new Expectations() {{ + myObject.wait(anyLong); + result = new Delegate() { + void wait(long timeoutMs) { + System.out.println("foo"); + System.out.println("bar"); + } + }; + }}; + myObject.wait(); + } + } + """, + """ + import mockit.Delegate; + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.anyLong; + import static org.mockito.Mockito.when; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + doAnswer(invocation -> { + System.out.println("foo"); + System.out.println("bar"); + return null; + }).when(myObject).wait(anyLong()); + myObject.wait(); + } + } + """ + ) + ); + } + + @Test + void whenNoArgsNonVoidMethod() { + //language=java + rewriteRun( + java( + """ + import mockit.Expectations; + import mockit.Delegate; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + import static org.junit.jupiter.api.Assertions.assertEquals; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + new Expectations() {{ + myObject.toString(); + result = new Delegate() { + String toString() { + String a = "foo"; + return a + "bar"; + } + }; + }}; + assertEquals("foobar", myObject.toString()); + } + } + """, + """ + import mockit.Delegate; + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.mockito.Mockito.when; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + when(myObject.toString()).thenAnswer(invocation -> { + String a = "foo"; + return a + "bar"; + }); + assertEquals("foobar", myObject.toString()); + } + } + """ + ) + ); + } + + @Test + void whenMultipleStatementsWithAnnotation() { + //language=java + rewriteRun( + java( + """ + import mockit.Expectations; + import mockit.Delegate; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + import static org.junit.jupiter.api.Assertions.assertEquals; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + new Expectations() {{ + myObject.hashCode(); + result = 100; + myObject.toString(); + result = new Delegate() { + @SuppressWarnings("unused") + String toString() { + String a = "foo"; + return a + "bar"; + } + }; + }}; + assertEquals(100, myObject.hashCode()); + assertEquals("foobar", myObject.toString()); + } + } + """, + """ + import mockit.Delegate; + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.mockito.Mockito.when; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + when(myObject.hashCode()).thenReturn(100); + when(myObject.toString()).thenAnswer(invocation -> { + String a = "foo"; + return a + "bar"; + }); + assertEquals(100, myObject.hashCode()); + assertEquals("foobar", myObject.toString()); + } + } + """ + ) + ); + } + + @Test + void whenClassArgumentMatcher() { + //language=java + rewriteRun( + java( + """ + import java.util.List; + + class MyObject { + public String getSomeField(List input) { + return "X"; + } + public String getSomeOtherField(Object input) { + return "Y"; + } + } + """ + ), + java( + """ + import java.util.ArrayList; + import java.util.List; + + import mockit.Delegate; + import mockit.Mocked; + import mockit.Expectations; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() { + new Expectations() {{ + myObject.getSomeField((List) any); + result = new Delegate() { + String getSomeOtherField(List input) { + input.add("foo"); + return input.toString(); + } + }; + }}; + myObject.getSomeField(new ArrayList<>()); + myObject.getSomeOtherField(new Object()); + } + } + """, + """ + import java.util.ArrayList; + import java.util.List; + + import mockit.Delegate; + + import static org.mockito.Mockito.anyList; + import static org.mockito.Mockito.when; + + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + MyObject myObject; + + void test() { + when(myObject.getSomeField(anyList())).thenAnswer(invocation -> { + List input = invocation.getArgument(0); + input.add("foo"); + return input.toString(); + }); + myObject.getSomeField(new ArrayList<>()); + myObject.getSomeOtherField(new Object()); + } + } + """ + ) + ); + } + +} diff --git a/src/test/java/org/openrewrite/java/testing/jmockit/JMockitExpectationsToMockitoTest.java b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitExpectationsToMockitoTest.java index 72d8cfe7d..29e107663 100644 --- a/src/test/java/org/openrewrite/java/testing/jmockit/JMockitExpectationsToMockitoTest.java +++ b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitExpectationsToMockitoTest.java @@ -15,36 +15,26 @@ */ package org.openrewrite.java.testing.jmockit; +import org.junit.jupiter.api.Disabled; 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; +import static org.openrewrite.java.testing.jmockit.JMockitTestUtils.setDefaultParserSettings; +@SuppressWarnings({"SpellCheckingInspection", "ResultOfMethodCallIgnored", "EmptyClassInitializer"}) class JMockitExpectationsToMockitoTest 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", - "mockito-core-3.12", - "mockito-junit-jupiter-3.12" - )) - .recipeFromResource( - "/META-INF/rewrite/jmockit.yml", - "org.openrewrite.java.testing.jmockit.JMockitToMockito" - ); + setDefaultParserSettings(spec); } @DocumentExample @Test - void voidResult() { + void whenTimesAndResult() { //language=java rewriteRun( java( @@ -53,12 +43,67 @@ void voidResult() { import mockit.Mocked; import mockit.integration.junit5.JMockitExtension; import org.junit.jupiter.api.extension.ExtendWith; - + + import static org.junit.jupiter.api.Assertions.assertEquals; + @ExtendWith(JMockitExtension.class) class MyTest { @Mocked Object myObject; + + void test() { + new Expectations() {{ + myObject.toString(); + result = "foo"; + times = 2; + }}; + assertEquals("foo", myObject.toString()); + assertEquals("foo", myObject.toString()); + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + when(myObject.toString()).thenReturn("foo"); + assertEquals("foo", myObject.toString()); + assertEquals("foo", myObject.toString()); + verify(myObject, times(2)).toString(); + } + } + """ + ) + ); + } + @DocumentExample + @Test + void whenNoResultNoTimes() { + //language=java + rewriteRun( + java( + """ + import mockit.Expectations; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + void test() { new Expectations() {{ myObject.wait(anyLong, anyInt); @@ -71,14 +116,113 @@ void test() { import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + + import static org.mockito.Mockito.*; + @ExtendWith(MockitoExtension.class) class MyTest { @Mock Object myObject; + + void test() { + myObject.wait(10L, 10); + verify(myObject).wait(anyLong(), anyInt()); + } + } + """ + ) + ); + } + @Test + void whenNoResultNoTimesNoArgs() { + //language=java + rewriteRun( + java( + """ + import mockit.Expectations; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + new Expectations() {{ + myObject.wait(); + }}; + myObject.wait(10L, 10); + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.verify; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + void test() { myObject.wait(10L, 10); + verify(myObject).wait(); + } + } + """ + ) + ); + } + + @Test + void whenHasResultNoTimes() { + //language=java + rewriteRun( + java( + """ + import mockit.Expectations; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + import static org.junit.jupiter.api.Assertions.assertEquals; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + new Expectations() {{ + myObject.toString(); + result = "foo"; + }}; + assertEquals("foo", myObject.toString()); + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.mockito.Mockito.when; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + when(myObject.toString()).thenReturn("foo"); + assertEquals("foo", myObject.toString()); } } """ @@ -107,12 +251,12 @@ public String getSomeField() { import org.junit.jupiter.api.extension.ExtendWith; import static org.junit.jupiter.api.Assertions.assertNull; - + @ExtendWith(JMockitExtension.class) class MyTest { @Mocked MyObject myObject; - + void test() { new Expectations() {{ myObject.getSomeField(); @@ -129,12 +273,12 @@ void test() { import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.when; - + @ExtendWith(MockitoExtension.class) class MyTest { @Mock MyObject myObject; - + void test() { when(myObject.getSomeField()).thenReturn(null); assertNull(myObject.getSomeField()); @@ -159,19 +303,19 @@ public int getSomeField() { """ ), java( - """ + """ import mockit.Expectations; import mockit.Mocked; import mockit.integration.junit5.JMockitExtension; import org.junit.jupiter.api.extension.ExtendWith; - + import static org.junit.jupiter.api.Assertions.assertEquals; - + @ExtendWith(JMockitExtension.class) class MyTest { @Mocked MyObject myObject; - + void test() { new Expectations() {{ myObject.getSomeField(); @@ -190,15 +334,15 @@ void test() { import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; - + @ExtendWith(MockitoExtension.class) class MyTest { @Mock MyObject myObject; - + void test() { when(myObject.getSomeField()).thenReturn(10); assertEquals(10, myObject.getSomeField()); @@ -225,19 +369,19 @@ public String getSomeField(String s) { """ ), java( - """ + """ import mockit.Expectations; import mockit.Mocked; import mockit.integration.junit5.JMockitExtension; import org.junit.jupiter.api.extension.ExtendWith; - + import static org.junit.jupiter.api.Assertions.assertEquals; - + @ExtendWith(JMockitExtension.class) class MyTest { @Mocked MyObject myObject; - + void test() { new Expectations() {{ myObject.getSomeField(anyString); @@ -251,16 +395,16 @@ void test() { import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.when; - + @ExtendWith(MockitoExtension.class) class MyTest { @Mock MyObject myObject; - + void test() { when(myObject.getSomeField(anyString())).thenReturn("foo"); assertEquals("foo", myObject.getSomeField("bar")); @@ -285,21 +429,21 @@ public String getSomeField() { """ ), java( - """ + """ import mockit.Expectations; import mockit.Mocked; import mockit.integration.junit5.JMockitExtension; import org.junit.jupiter.api.extension.ExtendWith; - + import static org.junit.jupiter.api.Assertions.assertEquals; - + @ExtendWith(JMockitExtension.class) class MyTest { @Mocked MyObject myObject; - + String expected = "expected"; - + void test() { new Expectations() {{ myObject.getSomeField(); @@ -313,17 +457,17 @@ void test() { import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; - + @ExtendWith(MockitoExtension.class) class MyTest { @Mock MyObject myObject; - + String expected = "expected"; - + void test() { when(myObject.getSomeField()).thenReturn(expected); assertEquals(expected, myObject.getSomeField()); @@ -348,19 +492,19 @@ public Object getSomeField() { """ ), java( - """ + """ import mockit.Expectations; import mockit.Mocked; import mockit.integration.junit5.JMockitExtension; import org.junit.jupiter.api.extension.ExtendWith; - + import static org.junit.jupiter.api.Assertions.assertNotNull; - + @ExtendWith(JMockitExtension.class) class MyTest { @Mocked MyObject myObject; - + void test() { new Expectations() {{ myObject.getSomeField(); @@ -374,15 +518,15 @@ void test() { import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.when; - + @ExtendWith(MockitoExtension.class) class MyTest { @Mock MyObject myObject; - + void test() { when(myObject.getSomeField()).thenReturn(new Object()); assertNotNull(myObject.getSomeField()); @@ -412,12 +556,12 @@ public String getSomeField() { import mockit.Mocked; import mockit.integration.junit5.JMockitExtension; import org.junit.jupiter.api.extension.ExtendWith; - + @ExtendWith(JMockitExtension.class) class MyTest { @Mocked MyObject myObject; - + void test() throws RuntimeException { new Expectations() {{ myObject.getSomeField(); @@ -431,14 +575,14 @@ void test() throws RuntimeException { import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + import static org.mockito.Mockito.when; - + @ExtendWith(MockitoExtension.class) class MyTest { @Mock MyObject myObject; - + void test() throws RuntimeException { when(myObject.getSomeField()).thenThrow(new RuntimeException()); myObject.getSomeField(); @@ -470,12 +614,12 @@ public String getSomeField() { import org.junit.jupiter.api.extension.ExtendWith; import static org.junit.jupiter.api.Assertions.assertEquals; - + @ExtendWith(JMockitExtension.class) class MyTest { @Mocked MyObject myObject; - + void test() throws RuntimeException { new Expectations() {{ myObject.getSomeField(); @@ -490,15 +634,15 @@ void test() throws RuntimeException { import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; - + @ExtendWith(MockitoExtension.class) class MyTest { @Mock MyObject myObject; - + void test() throws RuntimeException { when(myObject.getSomeField()).thenReturn("foo", "bar"); assertEquals("foo", myObject.getSomeField()); @@ -525,11 +669,14 @@ public String getSomeField(List input) { public String getSomeOtherField(Object input) { return "Y"; } + public String getSomeArrayField(Object input) { + return "Z"; + } } """ ), java( - """ + """ import java.util.ArrayList; import java.util.List; @@ -544,16 +691,19 @@ public String getSomeOtherField(Object input) { class MyTest { @Mocked MyObject myObject; - + void test() { new Expectations() {{ myObject.getSomeField((List) any); result = null; myObject.getSomeOtherField((Object) any); result = null; + myObject.getSomeArrayField((byte[]) any); + result = null; }}; assertNull(myObject.getSomeField(new ArrayList<>())); assertNull(myObject.getSomeOtherField(new Object())); + assertNull(myObject.getSomeArrayField(new byte[0])); } } """, @@ -572,12 +722,14 @@ void test() { class MyTest { @Mock MyObject myObject; - + void test() { when(myObject.getSomeField(anyList())).thenReturn(null); when(myObject.getSomeOtherField(any(Object.class))).thenReturn(null); + when(myObject.getSomeArrayField(any(byte[].class))).thenReturn(null); assertNull(myObject.getSomeField(new ArrayList<>())); assertNull(myObject.getSomeOtherField(new Object())); + assertNull(myObject.getSomeArrayField(new byte[0])); } } """ @@ -586,7 +738,7 @@ void test() { } @Test - void whenMixedArgumentMatcher() { + void whenNoArguments() { //language=java rewriteRun( java( @@ -594,14 +746,14 @@ void whenMixedArgumentMatcher() { import java.util.List; class MyObject { - public String getSomeField(String s, String s2, String s3, long l1) { + public String getSomeField() { return "X"; } } """ ), java( - """ + """ import java.util.ArrayList; import java.util.List; @@ -616,14 +768,13 @@ public String getSomeField(String s, String s2, String s3, long l1) { class MyTest { @Mocked MyObject myObject; - + void test() { - String bazz = "bazz"; new Expectations() {{ - myObject.getSomeField("foo", anyString, bazz, 10L); + myObject.getSomeField(); result = null; }}; - assertNull(myObject.getSomeField("foo", "bar", bazz, 10L)); + assertNull(myObject.getSomeField()); } } """, @@ -636,17 +787,16 @@ void test() { import org.mockito.junit.jupiter.MockitoExtension; import static org.junit.jupiter.api.Assertions.assertNull; - import static org.mockito.Mockito.*; + import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class MyTest { @Mock MyObject myObject; - + void test() { - String bazz = "bazz"; - when(myObject.getSomeField(eq("foo"), anyString(), eq(bazz), eq(10L))).thenReturn(null); - assertNull(myObject.getSomeField("foo", "bar", bazz, 10L)); + when(myObject.getSomeField()).thenReturn(null); + assertNull(myObject.getSomeField()); } } """ @@ -655,48 +805,117 @@ void test() { } @Test - void whenSetupStatements() { + void whenMixedArgumentMatcher() { //language=java rewriteRun( java( """ - class MyObject { + import java.util.List; - public String getSomeField(String s) { + class MyObject { + public String getSomeField(String s, String s2, String s3, long l1) { return "X"; } - public String getString() { - return "Y"; - } } """ ), java( - """ + """ + import java.util.ArrayList; + import java.util.List; + import mockit.Expectations; import mockit.Mocked; import mockit.integration.junit5.JMockitExtension; import org.junit.jupiter.api.extension.ExtendWith; - - import static org.junit.jupiter.api.Assertions.assertEquals; - + + import static org.junit.jupiter.api.Assertions.assertNull; + @ExtendWith(JMockitExtension.class) class MyTest { @Mocked MyObject myObject; - + void test() { - String a = "a"; - String s = "s"; - + String bazz = "bazz"; new Expectations() {{ - myObject.getSomeField(anyString); - result = s; - - myObject.getString(); - result = a; + myObject.getSomeField("foo", anyString, bazz, 10L); + result = null; }}; - + assertNull(myObject.getSomeField("foo", "bar", bazz, 10L)); + } + } + """, + """ + import java.util.ArrayList; + import java.util.List; + + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.junit.jupiter.api.Assertions.assertNull; + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + MyObject myObject; + + void test() { + String bazz = "bazz"; + when(myObject.getSomeField(eq("foo"), anyString(), eq(bazz), eq(10L))).thenReturn(null); + assertNull(myObject.getSomeField("foo", "bar", bazz, 10L)); + } + } + """ + ) + ); + } + + @Test + void whenSetupStatements() { + //language=java + rewriteRun( + java( + """ + class MyObject { + + public String getSomeField(String s) { + return "X"; + } + public String getString() { + return "Y"; + } + } + """ + ), + java( + """ + import mockit.Expectations; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + import static org.junit.jupiter.api.Assertions.assertEquals; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() { + String a = "a"; + String s = "s"; + + new Expectations() {{ + myObject.getSomeField(anyString); + result = s; + + myObject.getString(); + result = a; + }}; + assertEquals("s", myObject.getSomeField("foo")); assertEquals("a", myObject.getString()); } @@ -706,24 +925,22 @@ void test() { import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.when; - + @ExtendWith(MockitoExtension.class) class MyTest { @Mock MyObject myObject; - + void test() { String a = "a"; String s = "s"; - when(myObject.getSomeField(anyString())).thenReturn(s); - when(myObject.getString()).thenReturn(a); - + assertEquals("s", myObject.getSomeField("foo")); assertEquals("a", myObject.getString()); } @@ -747,19 +964,19 @@ public String getSomeField(String s) { """ ), java( - """ + """ import mockit.Expectations; import mockit.Mocked; import mockit.integration.junit5.JMockitExtension; import org.junit.jupiter.api.extension.ExtendWith; - + import static org.junit.jupiter.api.Assertions.assertEquals; - + @ExtendWith(JMockitExtension.class) class MyTest { @Mocked MyObject myObject; - + void test() { String a = "a"; new Expectations() {{ @@ -768,7 +985,7 @@ void test() { String b = "b"; result = s; }}; - + assertEquals("s", myObject.getSomeField("foo")); } } @@ -777,22 +994,22 @@ void test() { import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.when; - + @ExtendWith(MockitoExtension.class) class MyTest { @Mock MyObject myObject; - + void test() { String a = "a"; String s = "s"; String b = "b"; when(myObject.getSomeField(anyString())).thenReturn(s); - + assertEquals("s", myObject.getSomeField("foo")); } } @@ -806,28 +1023,125 @@ void whenTimes() { //language=java rewriteRun( java( - """ + """ import mockit.Expectations; import mockit.Mocked; import mockit.integration.junit5.JMockitExtension; import org.junit.jupiter.api.extension.ExtendWith; - + @ExtendWith(JMockitExtension.class) class MyTest { @Mocked Object myObject; - + void test() { new Expectations() {{ myObject.wait(anyLong, anyInt); - times = 2; + times = 3; + }}; + myObject.wait(10L, 10); + myObject.wait(10L, 10); + myObject.wait(10L, 10); + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(10L, 10); + myObject.wait(10L, 10); + myObject.wait(10L, 10); + verify(myObject, times(3)).wait(anyLong(), anyInt()); + } + } + """ + ) + ); + } + + @Test + void whenMinTimes() { + //language=java + rewriteRun( + java( + """ + import mockit.Expectations; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + new Expectations() {{ + myObject.wait(anyLong, anyInt); + minTimes = 2; }}; myObject.wait(10L, 10); + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(10L, 10); + verify(myObject, atLeast(2)).wait(anyLong(), anyInt()); + } + } + """ + ) + ); + } + + @Test + void whenMaxTimes() { + //language=java + rewriteRun( + java( + """ + import mockit.Expectations; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + new Expectations() {{ + myObject.wait(anyLong, anyInt); + maxTimes = 5; + }}; myObject.wait(10L, 10); } } """, - """ + """ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -838,11 +1152,59 @@ void test() { class MyTest { @Mock Object myObject; - + + void test() { + myObject.wait(10L, 10); + verify(myObject, atMost(5)).wait(anyLong(), anyInt()); + } + } + """ + ) + ); + } + + @Test + void whenMinTimesMaxTimes() { + //language=java + rewriteRun( + java( + """ + import mockit.Expectations; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + void test() { + new Expectations() {{ + myObject.wait(anyLong, anyInt); + minTimes = 1; + maxTimes = 3; + }}; myObject.wait(10L, 10); + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { myObject.wait(10L, 10); - verify(myObject, times(2)).wait(anyLong(), anyInt()); + verify(myObject, atLeast(1)).wait(anyLong(), anyInt()); + verify(myObject, atMost(3)).wait(anyLong(), anyInt()); } } """ @@ -869,14 +1231,14 @@ public String getSomeField() { import mockit.Tested; import mockit.integration.junit5.JMockitExtension; import org.junit.jupiter.api.extension.ExtendWith; - + import static org.junit.jupiter.api.Assertions.assertEquals; - + @ExtendWith(JMockitExtension.class) class MyTest { @Tested MyObject myObject; - + void test() { new Expectations(myObject) {{ myObject.getSomeField(); @@ -890,15 +1252,15 @@ void test() { import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.junit.jupiter.MockitoExtension; - + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; - + @ExtendWith(MockitoExtension.class) class MyTest { @InjectMocks MyObject myObject; - + void test() { when(myObject.getSomeField()).thenReturn("foo"); assertEquals("foo", myObject.getSomeField()); @@ -943,10 +1305,10 @@ public void doSomething() {} class MyTest { @Mocked Object myObject; - + @Mocked MyObject myOtherObject; - + void test() { new Expectations() {{ myObject.hashCode(); @@ -968,19 +1330,19 @@ void test() { import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.*; - + @ExtendWith(MockitoExtension.class) class MyTest { @Mock Object myObject; - + @Mock MyObject myOtherObject; - + void test() { when(myObject.hashCode()).thenReturn(10); when(myOtherObject.getSomeObjectField()).thenReturn(null); @@ -989,6 +1351,370 @@ void test() { assertNull(myOtherObject.getSomeObjectField()); myObject.wait(10L, 10); assertEquals("foo", myOtherObject.getSomeStringField("bar", 10L)); + verify(myObject).wait(anyLong(), anyInt()); + } + } + """ + ) + ); + } + + @Test + void whenMultipleExpectations() { + //language=java + rewriteRun( + java( + """ + class MyObject { + public String getSomeStringField() { + return "X"; + } + } + """ + ), + java( + """ + import mockit.Expectations; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertNull; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() { + new Expectations() {{ + myObject.getSomeStringField(); + result = "a"; + }}; + assertEquals("a", myObject.getSomeStringField()); + new Expectations() {{ + myObject.getSomeStringField(); + result = "b"; + }}; + assertEquals("b", myObject.getSomeStringField()); + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertNull; + import static org.mockito.Mockito.when; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + MyObject myObject; + + void test() { + when(myObject.getSomeStringField()).thenReturn("a"); + assertEquals("a", myObject.getSomeStringField()); + when(myObject.getSomeStringField()).thenReturn("b"); + assertEquals("b", myObject.getSomeStringField()); + } + } + """ + ) + ); + } + + @Test + void whenMultipleBlockInSingleExpectation() { + //language=java + rewriteRun( + java( + """ + class MyObject { + public String getX() { + return "X"; + } + public String getY() { + return "Y"; + } + } + """ + ), + java( + """ + import mockit.Expectations; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertNull; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() { + new Expectations() { + { + myObject.getX(); + result = "x1"; + } + { + myObject.getY(); + result = "y1"; + } + }; + assertEquals("x1", myObject.getX()); + assertEquals("y1", myObject.getY()); + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertNull; + import static org.mockito.Mockito.when; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + MyObject myObject; + + void test() { + when(myObject.getX()).thenReturn("x1"); + when(myObject.getY()).thenReturn("y1"); + assertEquals("x1", myObject.getX()); + assertEquals("y1", myObject.getY()); + } + } + """ + ) + ); + } + + @Test + void whenMultipleExpectationsNoResults() { + //language=java + rewriteRun( + java( + """ + import mockit.Expectations; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertNull; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + new Expectations() {{ + myObject.wait(anyLong); + }}; + myObject.wait(1L); + new Expectations() {{ + myObject.wait(); + }}; + myObject.wait(); + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertNull; + import static org.mockito.Mockito.anyLong; + import static org.mockito.Mockito.verify; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(1L); + myObject.wait(); + verify(myObject).wait(anyLong()); + verify(myObject).wait(); + } + } + """ + ) + ); + } + + @Test + void whenWithRedundantThisModifier() { + //language=java + rewriteRun( + java( + """ + import mockit.Expectations; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertNull; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + new Expectations() {{ + myObject.wait(this.anyLong, anyInt); + }}; + myObject.wait(); + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertNull; + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(); + verify(myObject).wait(anyLong(), anyInt()); + } + } + """ + ) + ); + } + + @Disabled // comment migration not supported yet + @Test + void whenComments() { + //language=java + rewriteRun( + java( + """ + class MyObject { + public String getSomeStringField() { + return "X"; + } + } + """ + ), + java( + """ + import mockit.Expectations; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertNull; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() { + new Expectations() {{ + // comments for this line below + myObject.getSomeStringField(); + result = "a"; + }}; + assertEquals("a", myObject.getSomeStringField()); + new Expectations() {{ + myObject.getSomeStringField(); + result = "b"; + }}; + assertEquals("b", myObject.getSomeStringField()); + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertNull; + import static org.mockito.Mockito.when; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + MyObject myObject; + + void test() { + // comments for this line below + when(myObject.getSomeStringField()).thenReturn("a"); + assertEquals("a", myObject.getSomeStringField()); + when(myObject.getSomeStringField()).thenReturn("b"); + assertEquals("b", myObject.getSomeStringField()); + } + } + """ + ) + ); + } + + @Test + void whenEmptyBlock() { + //language=java + rewriteRun( + java( + """ + import mockit.Expectations; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + new Expectations() {{ + }}; + myObject.wait(1L); + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(1L); } } """ diff --git a/src/test/java/org/openrewrite/java/testing/jmockit/JMockitFullVerificationsToMockitoTest.java b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitFullVerificationsToMockitoTest.java new file mode 100644 index 000000000..5eae411e5 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitFullVerificationsToMockitoTest.java @@ -0,0 +1,248 @@ +/* + * Copyright 2023 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.jmockit; + +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; + +/** + * Not doing comprehensive testing as it is covered in JMockitVerificationsToMockitoTest and shares same code path + */ +@SuppressWarnings("SpellCheckingInspection") +class JMockitFullVerificationsToMockitoTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + setDefaultParserSettings(spec); + } + + @DocumentExample + @Test + void whenMultipleMocks() { + //language=java + rewriteRun( + java( + """ + import mockit.FullVerifications; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + @Mocked + String str; + + void test() { + myObject.wait(10L, 10); + myObject.wait(10L, 10); + str.notify(); + new FullVerifications() {{ + myObject.wait(anyLong, anyInt); + times = 2; + str.notify(); + }}; + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + @Mock + String str; + + void test() { + myObject.wait(10L, 10); + myObject.wait(10L, 10); + str.notify(); + verify(myObject, times(2)).wait(anyLong(), anyInt()); + verify(str).notify(); + verifyNoMoreInteractions(myObject, str); + } + } + """ + ) + ); + } + + @Test + void whenTimes() { + //language=java + rewriteRun( + java( + """ + import mockit.FullVerifications; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + myObject.wait(10L, 10); + myObject.wait(10L, 10); + new FullVerifications() {{ + myObject.wait(anyLong, anyInt); + times = 2; + }}; + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(10L, 10); + myObject.wait(10L, 10); + verify(myObject, times(2)).wait(anyLong(), anyInt()); + verifyNoMoreInteractions(myObject); + } + } + """ + ) + ); + } + + @DocumentExample + @Test + void whenOtherStatements() { + //language=java + rewriteRun( + java( + """ + import mockit.FullVerifications; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + myObject.wait(10L, 10); + new FullVerifications() {{ + myObject.wait(anyLong, anyInt); + }}; + System.out.println("bla"); + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(10L, 10); + verify(myObject).wait(anyLong(), anyInt()); + verifyNoMoreInteractions(myObject); + System.out.println("bla"); + } + } + """ + ) + ); + } + + @Test + void whenMultipleInvocationsSameMock() { + //language=java + rewriteRun( + java( + """ + import mockit.FullVerifications; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + myObject.wait(10L, 10); + myObject.wait(); + new FullVerifications() {{ + myObject.wait(anyLong, anyInt); + myObject.wait(); + }}; + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(10L, 10); + myObject.wait(); + verify(myObject).wait(anyLong(), anyInt()); + verify(myObject).wait(); + verifyNoMoreInteractions(myObject); + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockitoTest.java b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockitoTest.java new file mode 100644 index 000000000..49c9ed48b --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitMockUpToMockitoTest.java @@ -0,0 +1,653 @@ +/* + * Copyright 2024 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.jmockit; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.SourceSpec; +import org.openrewrite.test.TypeValidation; + +import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.java.testing.jmockit.JMockitTestUtils.*; + +class JMockitMockUpToMockitoTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + setParserSettings(spec, JMOCKIT_DEPENDENCY, JUNIT_4_DEPENDENCY); + } + + @DocumentExample + @Test + void mockUpStaticMethodTest() { + //language=java + rewriteRun( + java( + """ + import mockit.Mock; + import mockit.MockUp; + import static org.junit.Assert.assertEquals; + import org.junit.Test; + + public class MockUpTest { + @Test + public void test() { + new MockUp() { + + @Mock + public int staticMethod() { + return 1024; + } + + @Mock + public int staticMethod(int v) { + return 128; + } + }; + assertEquals(1024, MyClazz.staticMethod()); + assertEquals(128, MyClazz.staticMethod(0)); + } + + public static class MyClazz { + public static int staticMethod() { + return 0; + } + + public static int staticMethod(int v) { + return 1; + } + } + } + """, """ + import static org.junit.Assert.assertEquals; + import static org.mockito.ArgumentMatchers.*; + import static org.mockito.Mockito.*; + + import org.junit.Test; + import org.mockito.MockedStatic; + + public class MockUpTest { + @Test + public void test() { + try (MockedStatic mockStaticMyClazz = mockStatic(MyClazz.class)) { + mockStaticMyClazz.when(() -> MyClazz.staticMethod()).thenAnswer(invocation -> 1024); + mockStaticMyClazz.when(() -> MyClazz.staticMethod(anyInt())).thenAnswer(invocation -> 128); + assertEquals(1024, MyClazz.staticMethod()); + assertEquals(128, MyClazz.staticMethod(0)); + } + } + + public static class MyClazz { + public static int staticMethod() { + return 0; + } + + public static int staticMethod(int v) { + return 1; + } + } + } + """)); + } + + @Test + void mockUpMultipleTest() { + //language=java + rewriteRun( + spec -> spec.afterTypeValidationOptions(TypeValidation.builder().identifiers(false).build()), + java( + """ + package com.openrewrite; + public static class Foo { + public String getMsg() { + return "foo"; + } + + public String getMsg(String echo) { + return "foo" + echo; + } + } + """, + SourceSpec::skip + ), + java( + """ + package com.openrewrite; + public static class Bar { + public String getMsg() { + return "bar"; + } + + public String getMsg(String echo) { + return "bar" + echo; + } + } + """, + SourceSpec::skip + ), + java( + """ + import com.openrewrite.Foo; + import com.openrewrite.Bar; + import org.junit.Test; + import mockit.Mock; + import mockit.MockUp; + import static org.junit.Assert.assertEquals; + + public class MockUpTest { + @Test + public void test() { + new MockUp() { + @Mock + public String getMsg() { + return "FOO"; + } + @Mock + public String getMsg(String echo) { + return "FOO" + echo; + } + }; + new MockUp() { + @Mock + public String getMsg() { + return "BAR"; + } + @Mock + public String getMsg(String echo) { + return "BAR" + echo; + } + }; + assertEquals("FOO", new Foo().getMsg()); + assertEquals("FOOecho", new Foo().getMsg("echo")); + assertEquals("BAR", new Bar().getMsg()); + assertEquals("BARecho", new Bar().getMsg("echo")); + } + } + """, """ + import com.openrewrite.Foo; + import com.openrewrite.Bar; + import org.junit.Test; + import org.mockito.MockedConstruction; + import static org.junit.Assert.assertEquals; + import static org.mockito.AdditionalAnswers.delegatesTo; + import static org.mockito.Answers.CALLS_REAL_METHODS; + import static org.mockito.ArgumentMatchers.*; + import static org.mockito.Mockito.*; + + public class MockUpTest { + @Test + public void test() { + Foo mockFoo = mock(Foo.class, CALLS_REAL_METHODS); + doAnswer(invocation -> "FOO").when(mockFoo).getMsg(); + doAnswer(invocation -> { + String echo = invocation.getArgument(0); + return "FOO" + echo; + }).when(mockFoo).getMsg(nullable(String.class)); + Bar mockBar = mock(Bar.class, CALLS_REAL_METHODS); + doAnswer(invocation -> "BAR").when(mockBar).getMsg(); + doAnswer(invocation -> { + String echo = invocation.getArgument(0); + return "BAR" + echo; + }).when(mockBar).getMsg(nullable(String.class)); + try (MockedConstruction mockConsFoo = mockConstructionWithAnswer(Foo.class, delegatesTo(mockFoo));MockedConstruction mockConsBar = mockConstructionWithAnswer(Bar.class, delegatesTo(mockBar))) { + assertEquals("FOO", new Foo().getMsg()); + assertEquals("FOOecho", new Foo().getMsg("echo")); + assertEquals("BAR", new Bar().getMsg()); + assertEquals("BARecho", new Bar().getMsg("echo")); + } + } + } + """) + ); + } + + @Test + void mockUpInnerStatementTest() { + //language=java + rewriteRun( + java( + """ + import mockit.Mock; + import mockit.MockUp; + + import org.junit.Test; + import static org.junit.Assert.assertEquals; + + public class MockUpTest { + @Test + public void test() { + new MockUp() { + final String msg = "newMsg"; + + @Mock + public String getMsg() { + return msg; + } + }; + + // Should ignore the newClass statement + new Runnable() { + @Override + public void run() { + System.out.println("run"); + } + }; + assertEquals("newMsg", new MyClazz().getMsg()); + } + + public static class MyClazz { + public String getMsg() { + return "msg"; + } + } + } + """, """ + import org.junit.Test; + import org.mockito.MockedConstruction; + + import static org.junit.Assert.assertEquals; + import static org.mockito.AdditionalAnswers.delegatesTo; + import static org.mockito.Answers.CALLS_REAL_METHODS; + import static org.mockito.Mockito.*; + + public class MockUpTest { + @Test + public void test() { + final String msg = "newMsg"; + MyClazz mockMyClazz = mock(MyClazz.class, CALLS_REAL_METHODS); + doAnswer(invocation -> msg).when(mockMyClazz).getMsg(); + try (MockedConstruction mockConsMyClazz = mockConstructionWithAnswer(MyClazz.class, delegatesTo(mockMyClazz))) { + + // Should ignore the newClass statement + new Runnable() { + @Override + public void run() { + System.out.println("run"); + } + }; + assertEquals("newMsg", new MyClazz().getMsg()); + } + } + + public static class MyClazz { + public String getMsg() { + return "msg"; + } + } + } + """)); + } + + @Test + void mockUpVoidTest() { + //language=java + rewriteRun( + java( + """ + import mockit.Mock; + import mockit.MockUp; + import static org.junit.Assert.assertEquals; + import org.junit.Test; + + public class MockUpTest { + @Test + public void test() { + new MockUp() { + @Mock + public void changeMsg() { + MockUpClass.Save.msg = "mockMsg"; + } + + @Mock + public void changeText(String text) { + MockUpClass.Save.text = "mockText"; + } + }; + + assertEquals("mockMsg", new MockUpClass().getMsg()); + assertEquals("mockText", new MockUpClass().getText()); + } + + public static class MockUpClass { + public static class Save { + public static String msg = "msg"; + public static String text = "text"; + } + + public final String getMsg() { + changeMsg(); + return Save.msg; + } + + public void changeMsg() { + Save.msg = "newMsg"; + } + + public String getText() { + changeText("newText"); + return Save.text; + } + + public static void changeText(String text) { + Save.text = text; + } + } + } + """, + """ + import static org.junit.Assert.assertEquals; + import static org.mockito.AdditionalAnswers.delegatesTo; + import static org.mockito.Answers.CALLS_REAL_METHODS; + import static org.mockito.ArgumentMatchers.*; + import static org.mockito.Mockito.*; + + import org.junit.Test; + import org.mockito.MockedConstruction; + import org.mockito.MockedStatic; + + public class MockUpTest { + @Test + public void test() { + MockUpClass mockMockUpClass = mock(MockUpClass.class, CALLS_REAL_METHODS); + doAnswer(invocation -> { + MockUpClass.Save.msg = "mockMsg"; + return null; + }).when(mockMockUpClass).changeMsg(); + try (MockedStatic mockStaticMockUpClass = mockStatic(MockUpClass.class);MockedConstruction mockConsMockUpClass = mockConstructionWithAnswer(MockUpClass.class, delegatesTo(mockMockUpClass))) { + mockStaticMockUpClass.when(() -> MockUpClass.changeText(nullable(String.class))).thenAnswer(invocation -> { + String text = invocation.getArgument(0); + MockUpClass.Save.text = "mockText"; + return null; + }); + + assertEquals("mockMsg", new MockUpClass().getMsg()); + assertEquals("mockText", new MockUpClass().getText()); + } + } + + public static class MockUpClass { + public static class Save { + public static String msg = "msg"; + public static String text = "text"; + } + + public final String getMsg() { + changeMsg(); + return Save.msg; + } + + public void changeMsg() { + Save.msg = "newMsg"; + } + + public String getText() { + changeText("newText"); + return Save.text; + } + + public static void changeText(String text) { + Save.text = text; + } + } + } + """)); + } + + @Test + void mockUpAtSetUpWithoutTearDownTest() { + rewriteRun( + //language=java + java( + """ + import org.junit.Before; + import org.junit.Test; + import mockit.Mock; + import mockit.MockUp; + import static org.junit.Assert.assertEquals; + + public class MockUpTest { + @Before + public void setUp() { + new MockUp() { + @Mock + public String getMsg() { + return "mockMsg"; + } + }; + } + + @Test + public void test() { + assertEquals("mockMsg", new MyClazz().getMsg()); + } + + public static class MyClazz { + public String getMsg() { + return "msg"; + } + } + } + """, + """ + import org.junit.After; + import org.junit.Before; + import org.junit.Test; + import org.mockito.MockedConstruction; + import static org.junit.Assert.assertEquals; + import static org.mockito.AdditionalAnswers.delegatesTo; + import static org.mockito.Answers.CALLS_REAL_METHODS; + import static org.mockito.Mockito.*; + + public class MockUpTest { + private MockedConstruction mockConsMyClazz; + + @Before + public void setUp() { + MyClazz mockMyClazz = mock(MyClazz.class, CALLS_REAL_METHODS); + doAnswer(invocation -> "mockMsg").when(mockMyClazz).getMsg(); + mockConsMyClazz = mockConstructionWithAnswer(MyClazz.class, delegatesTo(mockMyClazz)); + } + + @After + public void tearDown() { + mockConsMyClazz.closeOnDemand(); + } + + @Test + public void test() { + assertEquals("mockMsg", new MyClazz().getMsg()); + } + + public static class MyClazz { + public String getMsg() { + return "msg"; + } + } + } + """ + ) + ); + } + + @Test + void mockUpAtSetUpWithTearDownTest() { + rewriteRun( + //language=java + java( + """ + import org.junit.Before; + import org.junit.After; + import org.junit.Test; + import mockit.Mock; + import mockit.MockUp; + import static org.junit.Assert.assertEquals; + + public class MockUpTest { + @Before + public void setUp() { + new MockUp() { + @Mock + public String getMsg() { + return "mockMsg"; + } + + @Mock + public String getStaticMsg() { + return "mockStaticMsg"; + } + }; + } + + @After + public void tearDown() { + } + + @Test + public void test() { + assertEquals("mockMsg", new MyClazz().getMsg()); + assertEquals("mockStaticMsg", MyClazz.getStaticMsg()); + } + + public static class MyClazz { + public String getMsg() { + return "msg"; + } + + public static String getStaticMsg() { + return "staticMsg"; + } + } + } + """, + """ + import org.junit.Before; + import org.junit.After; + import org.junit.Test; + import org.mockito.MockedConstruction; + import org.mockito.MockedStatic; + import static org.junit.Assert.assertEquals; + import static org.mockito.AdditionalAnswers.delegatesTo; + import static org.mockito.Answers.CALLS_REAL_METHODS; + import static org.mockito.Mockito.*; + + public class MockUpTest { + private MockedConstruction mockConsMyClazz; + private MockedStatic mockStaticMyClazz; + + @Before + public void setUp() { + MyClazz mockMyClazz = mock(MyClazz.class, CALLS_REAL_METHODS); + doAnswer(invocation -> "mockMsg").when(mockMyClazz).getMsg(); + mockConsMyClazz = mockConstructionWithAnswer(MyClazz.class, delegatesTo(mockMyClazz)); + mockStaticMyClazz = mockStatic(MyClazz.class); + mockStaticMyClazz.when(() -> MyClazz.getStaticMsg()).thenAnswer(invocation -> "mockStaticMsg"); + } + + @After + public void tearDown() { + mockConsMyClazz.closeOnDemand(); + mockStaticMyClazz.closeOnDemand(); + } + + @Test + public void test() { + assertEquals("mockMsg", new MyClazz().getMsg()); + assertEquals("mockStaticMsg", MyClazz.getStaticMsg()); + } + + public static class MyClazz { + public String getMsg() { + return "msg"; + } + + public static String getStaticMsg() { + return "staticMsg"; + } + } + } + """ + ) + ); + } + + @Test + void mockUpWithParamsTest() { + rewriteRun( + //language=java + java( + """ + import mockit.Mock; + import mockit.MockUp; + import org.junit.Test; + + import static org.junit.Assert.assertEquals; + + public class MockUpTest { + @Test + public void init() { + new MockUp() { + @Mock + public String getMsg(String foo, String bar, String unused) { + return foo + bar; + } + }; + assertEquals("foobar", new MyClazz().getMsg("foo", "bar", "unused")); + } + + public static class MyClazz { + public String getMsg(String foo, String bar, String unused) { + return "msg"; + } + } + } + """, + """ + import org.junit.Test; + import org.mockito.MockedConstruction; + + import static org.junit.Assert.assertEquals; + import static org.mockito.AdditionalAnswers.delegatesTo; + import static org.mockito.Answers.CALLS_REAL_METHODS; + import static org.mockito.ArgumentMatchers.*; + import static org.mockito.Mockito.*; + + public class MockUpTest { + @Test + public void init() { + MyClazz mockMyClazz = mock(MyClazz.class, CALLS_REAL_METHODS); + doAnswer(invocation -> { + String foo = invocation.getArgument(0); + String bar = invocation.getArgument(1); + return foo + bar; + }).when(mockMyClazz).getMsg(nullable(String.class), nullable(String.class), nullable(String.class)); + try (MockedConstruction mockConsMyClazz = mockConstructionWithAnswer(MyClazz.class, delegatesTo(mockMyClazz))) { + assertEquals("foobar", new MyClazz().getMsg("foo", "bar", "unused")); + } + } + + public static class MyClazz { + public String getMsg(String foo, String bar, String unused) { + return "msg"; + } + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/jmockit/JMockitNonStrictExpectationsToMockitoTest.java b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitNonStrictExpectationsToMockitoTest.java new file mode 100644 index 000000000..463746a26 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitNonStrictExpectationsToMockitoTest.java @@ -0,0 +1,1185 @@ +/* + * Copyright 2023 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.jmockit; + +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.*; + +class JMockitNonStrictExpectationsToMockitoTest implements RewriteTest { + private static final String LEGACY_JMOCKIT_DEPENDENCY = "jmockit-1.22"; + + @Override + public void defaults(RecipeSpec spec) { + setParserSettings(spec, JUNIT_4_DEPENDENCY, LEGACY_JMOCKIT_DEPENDENCY, MOCKITO_CORE_DEPENDENCY); + } + + @Test + void whenNullResult() { + //language=java + rewriteRun( + java( + """ + class MyObject { + public String getSomeField() { + return "X"; + } + } + """ + ), + java( + """ + import mockit.NonStrictExpectations; + import mockit.Mocked; + import mockit.integration.junit4.JMockit; + import org.junit.runner.RunWith; + + import static org.junit.Assert.assertNull; + + @RunWith(JMockit.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() { + new NonStrictExpectations() {{ + myObject.getSomeField(); + result = null; + }}; + assertNull(myObject.getSomeField()); + } + } + """, + """ + import org.junit.runner.RunWith; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnitRunner; + + import static org.junit.Assert.assertNull; + import static org.mockito.Mockito.lenient; + import static org.mockito.Mockito.when; + + @RunWith(MockitoJUnitRunner.class) + class MyTest { + @Mock + MyObject myObject; + + void test() { + lenient().when(myObject.getSomeField()).thenReturn(null); + assertNull(myObject.getSomeField()); + } + } + """ + ) + ); + } + + @Test + void whenIntResult() { + //language=java + rewriteRun( + java( + """ + class MyObject { + public int getSomeField() { + return 0; + } + } + """ + ), + java( + """ + import mockit.NonStrictExpectations; + import mockit.Mocked; + import mockit.integration.junit4.JMockit; + import org.junit.runner.RunWith; + + import static org.junit.Assert.assertEquals; + + @RunWith(JMockit.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() { + new NonStrictExpectations() {{ + myObject.getSomeField(); + result = 10; + }}; + assertEquals(10, myObject.getSomeField()); + new NonStrictExpectations() {{ + myObject.getSomeField(); + this.result = 100; + }}; + assertEquals(100, myObject.getSomeField()); + } + } + """, + """ + import org.junit.runner.RunWith; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnitRunner; + + import static org.junit.Assert.assertEquals; + import static org.mockito.Mockito.lenient; + import static org.mockito.Mockito.when; + + @RunWith(MockitoJUnitRunner.class) + class MyTest { + @Mock + MyObject myObject; + + void test() { + lenient().when(myObject.getSomeField()).thenReturn(10); + assertEquals(10, myObject.getSomeField()); + lenient().when(myObject.getSomeField()).thenReturn(100); + assertEquals(100, myObject.getSomeField()); + } + } + """ + ) + ); + } + + @Test + void whenStringResult() { + //language=java + rewriteRun( + java( + """ + class MyObject { + public String getSomeField(String s) { + return "X"; + } + } + """ + ), + java( + """ + import mockit.NonStrictExpectations; + import mockit.Mocked; + import mockit.integration.junit4.JMockit; + import org.junit.runner.RunWith; + + import static org.junit.Assert.assertEquals; + + @RunWith(JMockit.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() { + new NonStrictExpectations() {{ + myObject.getSomeField(anyString); + result = "foo"; + }}; + assertEquals("foo", myObject.getSomeField("bar")); + } + } + """, + """ + import org.junit.runner.RunWith; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnitRunner; + + import static org.junit.Assert.assertEquals; + import static org.mockito.Mockito.*; + + @RunWith(MockitoJUnitRunner.class) + class MyTest { + @Mock + MyObject myObject; + + void test() { + lenient().when(myObject.getSomeField(anyString())).thenReturn("foo"); + assertEquals("foo", myObject.getSomeField("bar")); + } + } + """ + ) + ); + } + + @Test + void whenVariableResult() { + //language=java + rewriteRun( + java( + """ + class MyObject { + public String getSomeField() { + return "X"; + } + } + """ + ), + java( + """ + import mockit.NonStrictExpectations; + import mockit.Mocked; + import mockit.integration.junit4.JMockit; + import org.junit.runner.RunWith; + + import static org.junit.Assert.assertEquals; + + @RunWith(JMockit.class) + class MyTest { + @Mocked + MyObject myObject; + + String expected = "expected"; + + void test() { + new NonStrictExpectations() {{ + myObject.getSomeField(); + result = expected; + }}; + assertEquals(expected, myObject.getSomeField()); + } + } + """, + """ + import org.junit.runner.RunWith; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnitRunner; + + import static org.junit.Assert.assertEquals; + import static org.mockito.Mockito.lenient; + import static org.mockito.Mockito.when; + + @RunWith(MockitoJUnitRunner.class) + class MyTest { + @Mock + MyObject myObject; + + String expected = "expected"; + + void test() { + lenient().when(myObject.getSomeField()).thenReturn(expected); + assertEquals(expected, myObject.getSomeField()); + } + } + """ + ) + ); + } + + @Test + void whenNewClassResult() { + //language=java + rewriteRun( + java( + """ + class MyObject { + public Object getSomeField() { + return null; + } + } + """ + ), + java( + """ + import mockit.NonStrictExpectations; + import mockit.Mocked; + import mockit.integration.junit4.JMockit; + import org.junit.runner.RunWith; + + import static org.junit.Assert.assertNotNull; + + @RunWith(JMockit.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() { + new NonStrictExpectations() {{ + myObject.getSomeField(); + result = new Object(); + }}; + assertNotNull(myObject.getSomeField()); + } + } + """, + """ + import org.junit.runner.RunWith; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnitRunner; + + import static org.junit.Assert.assertNotNull; + import static org.mockito.Mockito.lenient; + import static org.mockito.Mockito.when; + + @RunWith(MockitoJUnitRunner.class) + class MyTest { + @Mock + MyObject myObject; + + void test() { + lenient().when(myObject.getSomeField()).thenReturn(new Object()); + assertNotNull(myObject.getSomeField()); + } + } + """ + ) + ); + } + + @Test + void whenExceptionResult() { + //language=java + rewriteRun( + java( + """ + class MyObject { + public String getSomeField() { + return "X"; + } + } + """ + ), + java( + """ + import mockit.NonStrictExpectations; + import mockit.Mocked; + import mockit.integration.junit4.JMockit; + import org.junit.runner.RunWith; + + @RunWith(JMockit.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() throws RuntimeException { + new NonStrictExpectations() {{ + myObject.getSomeField(); + result = new RuntimeException(); + }}; + myObject.getSomeField(); + } + } + """, + """ + import org.junit.runner.RunWith; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnitRunner; + + import static org.mockito.Mockito.lenient; + import static org.mockito.Mockito.when; + + @RunWith(MockitoJUnitRunner.class) + class MyTest { + @Mock + MyObject myObject; + + void test() throws RuntimeException { + lenient().when(myObject.getSomeField()).thenThrow(new RuntimeException()); + myObject.getSomeField(); + } + } + """ + ) + ); + } + + @Test + void whenReturns() { + //language=java + rewriteRun( + java( + """ + class MyObject { + public String getSomeField() { + return "X"; + } + } + """ + ), + java( + """ + import mockit.NonStrictExpectations; + import mockit.Mocked; + import mockit.integration.junit4.JMockit; + import org.junit.runner.RunWith; + + import static org.junit.Assert.assertEquals; + + @RunWith(JMockit.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() throws RuntimeException { + new NonStrictExpectations() {{ + myObject.getSomeField(); + returns("foo", "bar"); + }}; + assertEquals("foo", myObject.getSomeField()); + assertEquals("bar", myObject.getSomeField()); + } + } + """, + """ + import org.junit.runner.RunWith; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnitRunner; + + import static org.junit.Assert.assertEquals; + import static org.mockito.Mockito.lenient; + import static org.mockito.Mockito.when; + + @RunWith(MockitoJUnitRunner.class) + class MyTest { + @Mock + MyObject myObject; + + void test() throws RuntimeException { + lenient().when(myObject.getSomeField()).thenReturn("foo", "bar"); + assertEquals("foo", myObject.getSomeField()); + assertEquals("bar", myObject.getSomeField()); + } + } + """ + ) + ); + } + + @Test + void whenClassArgumentMatcher() { + //language=java + rewriteRun( + java( + """ + import java.util.List; + + class MyObject { + public String getSomeField(List input) { + return "X"; + } + public String getSomeOtherField(Object input) { + return "Y"; + } + } + """ + ), + java( + """ + import java.util.ArrayList; + import java.util.List; + + import mockit.NonStrictExpectations; + import mockit.Mocked; + import mockit.integration.junit4.JMockit; + import org.junit.runner.RunWith; + + import static org.junit.Assert.assertNull; + + @RunWith(JMockit.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() { + new NonStrictExpectations() {{ + myObject.getSomeField((List) any); + result = null; + myObject.getSomeOtherField((Object) any); + result = null; + }}; + assertNull(myObject.getSomeField(new ArrayList<>())); + assertNull(myObject.getSomeOtherField(new Object())); + } + } + """, + """ + import java.util.ArrayList; + import java.util.List; + + import org.junit.runner.RunWith; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnitRunner; + + import static org.junit.Assert.assertNull; + import static org.mockito.Mockito.*; + + @RunWith(MockitoJUnitRunner.class) + class MyTest { + @Mock + MyObject myObject; + + void test() { + lenient().when(myObject.getSomeField(anyList())).thenReturn(null); + lenient().when(myObject.getSomeOtherField(any(Object.class))).thenReturn(null); + assertNull(myObject.getSomeField(new ArrayList<>())); + assertNull(myObject.getSomeOtherField(new Object())); + } + } + """ + ) + ); + } + + @Test + void whenNoArguments() { + //language=java + rewriteRun( + java( + """ + import java.util.List; + + class MyObject { + public String getSomeField() { + return "X"; + } + } + """ + ), + java( + """ + import java.util.ArrayList; + import java.util.List; + + import mockit.NonStrictExpectations; + import mockit.Mocked; + import mockit.integration.junit4.JMockit; + import org.junit.runner.RunWith; + + import static org.junit.Assert.assertNull; + + @RunWith(JMockit.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() { + new NonStrictExpectations() {{ + myObject.getSomeField(); + result = null; + }}; + assertNull(myObject.getSomeField()); + } + } + """, + """ + import java.util.ArrayList; + import java.util.List; + + import org.junit.runner.RunWith; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnitRunner; + + import static org.junit.Assert.assertNull; + import static org.mockito.Mockito.lenient; + import static org.mockito.Mockito.when; + + @RunWith(MockitoJUnitRunner.class) + class MyTest { + @Mock + MyObject myObject; + + void test() { + lenient().when(myObject.getSomeField()).thenReturn(null); + assertNull(myObject.getSomeField()); + } + } + """ + ) + ); + } + + @DocumentExample + @Test + void whenMixedArgumentMatcher() { + //language=java + rewriteRun( + java( + """ + import java.util.List; + + class MyObject { + public String getSomeField(String s, String s2, String s3, long l1) { + return "X"; + } + } + """ + ), + java( + """ + import java.util.ArrayList; + import java.util.List; + + import mockit.NonStrictExpectations; + import mockit.Mocked; + import mockit.integration.junit4.JMockit; + import org.junit.runner.RunWith; + + import static org.junit.Assert.assertNull; + + @RunWith(JMockit.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() { + String bazz = "bazz"; + new NonStrictExpectations() {{ + myObject.getSomeField("foo", anyString, bazz, 10L); + result = null; + }}; + assertNull(myObject.getSomeField("foo", "bar", bazz, 10L)); + } + } + """, + """ + import java.util.ArrayList; + import java.util.List; + + import org.junit.runner.RunWith; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnitRunner; + + import static org.junit.Assert.assertNull; + import static org.mockito.Mockito.*; + + @RunWith(MockitoJUnitRunner.class) + class MyTest { + @Mock + MyObject myObject; + + void test() { + String bazz = "bazz"; + lenient().when(myObject.getSomeField(eq("foo"), anyString(), eq(bazz), eq(10L))).thenReturn(null); + assertNull(myObject.getSomeField("foo", "bar", bazz, 10L)); + } + } + """ + ) + ); + } + + @Test + void whenSetupStatements() { + //language=java + rewriteRun( + java( + """ + class MyObject { + + public String getSomeField(String s) { + return "X"; + } + public String getString() { + return "Y"; + } + } + """ + ), + java( + """ + import mockit.NonStrictExpectations; + import mockit.Mocked; + import mockit.integration.junit4.JMockit; + import org.junit.runner.RunWith; + + import static org.junit.Assert.assertEquals; + + @RunWith(JMockit.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() { + String a = "a"; + String s = "s"; + + new NonStrictExpectations() {{ + myObject.getSomeField(anyString); + result = s; + + myObject.getString(); + result = a; + }}; + + assertEquals("s", myObject.getSomeField("foo")); + assertEquals("a", myObject.getString()); + } + } + """, + """ + import org.junit.runner.RunWith; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnitRunner; + + import static org.junit.Assert.assertEquals; + import static org.mockito.Mockito.*; + + @RunWith(MockitoJUnitRunner.class) + class MyTest { + @Mock + MyObject myObject; + + void test() { + String a = "a"; + String s = "s"; + lenient().when(myObject.getSomeField(anyString())).thenReturn(s); + lenient().when(myObject.getString()).thenReturn(a); + + assertEquals("s", myObject.getSomeField("foo")); + assertEquals("a", myObject.getString()); + } + } + """ + ) + ); + } + + @Test + void whenSetupStatements2() { + //language=java + rewriteRun( + java( + """ + class MyObject { + public String getSomeField(String s) { + return "X"; + } + } + """ + ), + java( + """ + import mockit.NonStrictExpectations; + import mockit.Mocked; + import mockit.integration.junit4.JMockit; + import org.junit.runner.RunWith; + + import static org.junit.Assert.assertEquals; + + @RunWith(JMockit.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() { + String a = "a"; + new NonStrictExpectations() {{ + myObject.getSomeField(anyString); + String s = "s"; + String b = "b"; + result = s; + }}; + + assertEquals("s", myObject.getSomeField("foo")); + } + } + """, + """ + import org.junit.runner.RunWith; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnitRunner; + + import static org.junit.Assert.assertEquals; + import static org.mockito.Mockito.*; + + @RunWith(MockitoJUnitRunner.class) + class MyTest { + @Mock + MyObject myObject; + + void test() { + String a = "a"; + String s = "s"; + String b = "b"; + lenient().when(myObject.getSomeField(anyString())).thenReturn(s); + + assertEquals("s", myObject.getSomeField("foo")); + } + } + """ + ) + ); + } + + @Test + void whenSpy() { + //language=java + rewriteRun( + java( + """ + class MyObject { + public String getSomeField() { + return "X"; + } + } + """ + ), + java( + """ + import mockit.NonStrictExpectations; + import mockit.Tested; + import mockit.integration.junit4.JMockit; + import org.junit.runner.RunWith; + + import static org.junit.Assert.assertEquals; + + @RunWith(JMockit.class) + class MyTest { + @Tested + MyObject myObject; + + void test() { + new NonStrictExpectations(myObject) {{ + myObject.getSomeField(); + result = "foo"; + }}; + assertEquals("foo", myObject.getSomeField()); + } + } + """, + """ + import org.junit.runner.RunWith; + import org.mockito.InjectMocks; + import org.mockito.junit.MockitoJUnitRunner; + + import static org.junit.Assert.assertEquals; + import static org.mockito.Mockito.lenient; + import static org.mockito.Mockito.when; + + @RunWith(MockitoJUnitRunner.class) + class MyTest { + @InjectMocks + MyObject myObject; + + void test() { + lenient().when(myObject.getSomeField()).thenReturn("foo"); + assertEquals("foo", myObject.getSomeField()); + } + } + """ + ) + ); + } + + @Test + void whenMultipleStatements() { + //language=java + rewriteRun( + java( + """ + class MyObject { + public String getSomeStringField(String input, long otherInput) { + return "X"; + } + public int getSomeIntField() { + return 0; + } + public Object getSomeObjectField() { + return new Object(); + } + public void doSomething() {} + } + """ + ), + java( + """ + import mockit.NonStrictExpectations; + import mockit.Mocked; + import mockit.integration.junit4.JMockit; + import org.junit.runner.RunWith; + + import static org.junit.Assert.assertEquals; + import static org.junit.Assert.assertNull; + + @RunWith(JMockit.class) + class MyTest { + @Mocked + Object myObject; + + @Mocked + MyObject myOtherObject; + + void test() { + new NonStrictExpectations() {{ + myObject.hashCode(); + result = 10; + myOtherObject.getSomeObjectField(); + result = null; + myObject.wait(anyLong, anyInt); + myOtherObject.getSomeStringField(anyString, anyLong); + result = "foo"; + }}; + assertEquals(10, myObject.hashCode()); + assertNull(myOtherObject.getSomeObjectField()); + myObject.wait(10L, 10); + assertEquals("foo", myOtherObject.getSomeStringField("bar", 10L)); + } + } + """, + """ + import org.junit.runner.RunWith; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnitRunner; + + import static org.junit.Assert.assertEquals; + import static org.junit.Assert.assertNull; + import static org.mockito.Mockito.*; + + @RunWith(MockitoJUnitRunner.class) + class MyTest { + @Mock + Object myObject; + + @Mock + MyObject myOtherObject; + + void test() { + lenient().when(myObject.hashCode()).thenReturn(10); + lenient().when(myOtherObject.getSomeObjectField()).thenReturn(null); + lenient().when(myOtherObject.getSomeStringField(anyString(), anyLong())).thenReturn("foo"); + assertEquals(10, myObject.hashCode()); + assertNull(myOtherObject.getSomeObjectField()); + myObject.wait(10L, 10); + assertEquals("foo", myOtherObject.getSomeStringField("bar", 10L)); + } + } + """ + ) + ); + } + + @Test + void whenMultipleExpectations() { + //language=java + rewriteRun( + java( + """ + class MyObject { + public String getSomeStringField() { + return "X"; + } + } + """ + ), + java( + """ + import mockit.NonStrictExpectations; + import mockit.Mocked; + import mockit.integration.junit4.JMockit; + import org.junit.runner.RunWith; + + import static org.junit.Assert.assertEquals; + import static org.junit.Assert.assertNull; + + @RunWith(JMockit.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() { + new NonStrictExpectations() {{ + myObject.getSomeStringField(); + result = "a"; + }}; + assertEquals("a", myObject.getSomeStringField()); + new NonStrictExpectations() {{ + myObject.getSomeStringField(); + result = "b"; + }}; + assertEquals("b", myObject.getSomeStringField()); + } + } + """, + """ + import org.junit.runner.RunWith; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnitRunner; + + import static org.junit.Assert.assertEquals; + import static org.junit.Assert.assertNull; + import static org.mockito.Mockito.lenient; + import static org.mockito.Mockito.when; + + @RunWith(MockitoJUnitRunner.class) + class MyTest { + @Mock + MyObject myObject; + + void test() { + lenient().when(myObject.getSomeStringField()).thenReturn("a"); + assertEquals("a", myObject.getSomeStringField()); + lenient().when(myObject.getSomeStringField()).thenReturn("b"); + assertEquals("b", myObject.getSomeStringField()); + } + } + """ + ) + ); + } + + @Test + void whenNoResultsNoTimes() { + //language=java + rewriteRun( + java( + """ + import mockit.NonStrictExpectations; + import mockit.Mocked; + import mockit.integration.junit4.JMockit; + import org.junit.runner.RunWith; + + import static org.junit.Assert.assertEquals; + import static org.junit.Assert.assertNull; + + @RunWith(JMockit.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + new NonStrictExpectations() {{ + myObject.wait(anyLong); + }}; + myObject.wait(1L); + } + } + """, + """ + import org.junit.runner.RunWith; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnitRunner; + + import static org.junit.Assert.assertEquals; + import static org.junit.Assert.assertNull; + + @RunWith(MockitoJUnitRunner.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(1L); + } + } + """ + ) + ); + } + + @Test + void whenTimes() { + //language=java + rewriteRun( + java( + """ + import mockit.NonStrictExpectations; + import mockit.Mocked; + import mockit.integration.junit4.JMockit; + import org.junit.runner.RunWith; + + @RunWith(JMockit.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + new NonStrictExpectations() {{ + myObject.wait(anyLong, anyInt); + times = 3; + }}; + myObject.wait(10L, 10); + myObject.wait(10L, 10); + myObject.wait(10L, 10); + } + } + """, + """ + import org.junit.runner.RunWith; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnitRunner; + + import static org.mockito.Mockito.*; + + @RunWith(MockitoJUnitRunner.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(10L, 10); + myObject.wait(10L, 10); + myObject.wait(10L, 10); + verify(myObject, times(3)).wait(anyLong(), anyInt()); + } + } + """ + ) + ); + } + + @Test + void whenTimesAndResult() { + //language=java + rewriteRun( + java( + """ + import mockit.NonStrictExpectations; + import mockit.Mocked; + import mockit.integration.junit4.JMockit; + import org.junit.runner.RunWith; + + @RunWith(JMockit.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + new NonStrictExpectations() {{ + myObject.toString(); + result = "foo"; + times = 2; + }}; + myObject.toString(); + myObject.toString(); + } + } + """, + """ + import org.junit.runner.RunWith; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnitRunner; + + import static org.mockito.Mockito.*; + + @RunWith(MockitoJUnitRunner.class) + class MyTest { + @Mock + Object myObject; + + void test() { + when(myObject.toString()).thenReturn("foo"); + myObject.toString(); + myObject.toString(); + verify(myObject, times(2)).toString(); + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/jmockit/JMockitTestUtils.java b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitTestUtils.java new file mode 100644 index 000000000..9c2ac5f31 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitTestUtils.java @@ -0,0 +1,50 @@ +/* + * Copyright 2023 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.jmockit; + +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; + +@SuppressWarnings("SpellCheckingInspection") +public class JMockitTestUtils { + + static final String MOCKITO_CORE_DEPENDENCY = "mockito-core-3.12"; + static final String JUNIT_5_JUPITER_DEPENDENCY = "junit-jupiter-api-5.9"; + static final String JUNIT_4_DEPENDENCY = "junit-4.13.2"; + static final String JMOCKIT_DEPENDENCY = "jmockit-1.49"; + static final String MOCKITO_JUPITER_DEPENDENCY = "mockito-junit-jupiter-3.12"; + + static void setDefaultParserSettings(RecipeSpec spec) { + setParserSettings(spec, + JUNIT_5_JUPITER_DEPENDENCY, + JMOCKIT_DEPENDENCY, + MOCKITO_CORE_DEPENDENCY, + MOCKITO_JUPITER_DEPENDENCY); + } + + static void setParserSettings(RecipeSpec spec, String... javaParserTestDependencies) { + spec.parser(JavaParser.fromJavaVersion() + .logCompilationWarningsAndErrors(true) + .classpathFromResources(new InMemoryExecutionContext(), + javaParserTestDependencies + )) + .recipeFromResource( + "/META-INF/rewrite/jmockit.yml", + "org.openrewrite.java.testing.jmockit.JMockitToMockito" + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/jmockit/JMockitVerificationsToMockitoTest.java b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitVerificationsToMockitoTest.java new file mode 100644 index 000000000..c4cae4b23 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/jmockit/JMockitVerificationsToMockitoTest.java @@ -0,0 +1,950 @@ +/* + * Copyright 2023 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.jmockit; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; + +import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.java.testing.jmockit.JMockitTestUtils.setDefaultParserSettings; + +@SuppressWarnings({"SpellCheckingInspection", "ResultOfMethodCallIgnored"}) +class JMockitVerificationsToMockitoTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + setDefaultParserSettings(spec); + } + + @DocumentExample + @Test + void whenNoTimes() { + //language=java + rewriteRun( + java( + """ + import mockit.Verifications; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + myObject.wait(10L, 10); + new Verifications() {{ + myObject.wait(anyLong, anyInt); + }}; + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(10L, 10); + verify(myObject).wait(anyLong(), anyInt()); + } + } + """ + ) + ); + } + + @DocumentExample + @Test + void whenNoTimesNoArgs() { + //language=java + rewriteRun( + java( + """ + import mockit.Verifications; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + myObject.wait(10L, 10); + new Verifications() {{ + myObject.wait(); + }}; + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.verify; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(10L, 10); + verify(myObject).wait(); + } + } + """ + ) + ); + } + + + @Test + void whenTimesNoArgs() { + //language=java + rewriteRun( + java( + """ + import java.util.ArrayList; + import java.util.List; + + import mockit.Verifications; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + myObject.wait(); + myObject.wait(); + new Verifications() {{ + myObject.wait(); + times = 2; + }}; + } + } + """, + """ + import java.util.ArrayList; + import java.util.List; + + import static org.mockito.Mockito.times; + import static org.mockito.Mockito.verify; + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(); + myObject.wait(); + verify(myObject, times(2)).wait(); + } + } + """ + ) + ); + } + + @Test + void whenClassArgumentMatcher() { + //language=java + rewriteRun( + spec -> spec.afterTypeValidationOptions(TypeValidation.builder().methodInvocations(false).build()), + java( + """ + import java.util.List; + + class MyObject { + public String getSomeField(List input) { + return "X"; + } + public String getSomeOtherField(Object input) { + return "Y"; + } + } + """ + ), + java( + """ + import java.util.ArrayList; + import java.util.List; + + import mockit.Mocked; + import mockit.Verifications; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() { + myObject.getSomeField(new ArrayList<>()); + myObject.getSomeOtherField(new Object()); + new Verifications() {{ + myObject.getSomeField((List) any); + myObject.getSomeOtherField((Object) any); + }}; + } + } + """, + """ + import java.util.ArrayList; + import java.util.List; + + import static org.mockito.Mockito.*; + + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + MyObject myObject; + + void test() { + myObject.getSomeField(new ArrayList<>()); + myObject.getSomeOtherField(new Object()); + verify(myObject).getSomeField(anyList()); + verify(myObject).getSomeOtherField(any(Object.class)); + } + } + """ + ) + ); + } + + + @Test + void whenMixedArgumentMatcher() { + //language=java + rewriteRun( + java( + """ + import java.util.ArrayList; + import java.util.List; + + import mockit.Verifications; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + myObject.wait(10L, 10); + new Verifications() {{ + myObject.wait(anyLong, 10); + }}; + } + } + """, + """ + import java.util.ArrayList; + import java.util.List; + + import static org.mockito.Mockito.*; + + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(10L, 10); + verify(myObject).wait(anyLong(), eq(10)); + } + } + """ + ) + ); + } + + @Test + void whenSetupStatements() { + //language=java + rewriteRun( + java( + """ + import mockit.Verifications; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + String a = "a"; + String s = "s"; + myObject.wait(1L); + myObject.wait(); + new Verifications() {{ + myObject.wait(anyLong); + myObject.wait(); + }}; + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.anyLong; + import static org.mockito.Mockito.verify; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + String a = "a"; + String s = "s"; + myObject.wait(1L); + myObject.wait(); + verify(myObject).wait(anyLong()); + verify(myObject).wait(); + } + } + """ + ) + ); + } + + @Test + void whenSetupStatements2() { + //language=java + rewriteRun( + java( + """ + import mockit.Verifications; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + String a = "a"; + myObject.wait(1L); + new Verifications() {{ + myObject.wait(anyLong); + }}; + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.anyLong; + import static org.mockito.Mockito.verify; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + String a = "a"; + myObject.wait(1L); + verify(myObject).wait(anyLong()); + } + } + """ + ) + ); + } + + @Test + void whenTimes() { + //language=java + rewriteRun( + java( + """ + import mockit.Verifications; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + myObject.wait(10L, 10); + myObject.wait(10L, 10); + myObject.wait(10L, 10); + new Verifications() {{ + myObject.wait(anyLong, anyInt); + times = 3; + }}; + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(10L, 10); + myObject.wait(10L, 10); + myObject.wait(10L, 10); + verify(myObject, times(3)).wait(anyLong(), anyInt()); + } + } + """ + ) + ); + } + + @Test + void whenMinTimes() { + //language=java + rewriteRun( + java( + """ + import mockit.Verifications; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + myObject.wait(10L, 10); + new Verifications() {{ + myObject.wait(anyLong, anyInt); + minTimes = 2; + }}; + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(10L, 10); + verify(myObject, atLeast(2)).wait(anyLong(), anyInt()); + } + } + """ + ) + ); + } + + @Test + void whenMaxTimes() { + //language=java + rewriteRun( + java( + """ + import mockit.Verifications; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + myObject.wait(10L, 10); + new Verifications() {{ + myObject.wait(anyLong, anyInt); + maxTimes = 5; + }}; + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(10L, 10); + verify(myObject, atMost(5)).wait(anyLong(), anyInt()); + } + } + """ + ) + ); + } + + @Test + void whenMinTimesMaxTimes() { + //language=java + rewriteRun( + java( + """ + import mockit.Verifications; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + myObject.wait(10L, 10); + new Verifications() {{ + myObject.wait(anyLong, anyInt); + minTimes = 1; + maxTimes = 3; + }}; + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(10L, 10); + verify(myObject, atLeast(1)).wait(anyLong(), anyInt()); + verify(myObject, atMost(3)).wait(anyLong(), anyInt()); + } + } + """ + ) + ); + } + + @Test + void whenMultipleStatements() { + //language=java + rewriteRun( + java( + """ + import mockit.Verifications; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + @Mocked + Object myOtherObject; + + void test() { + myObject.hashCode(); + myOtherObject.wait(); + myObject.wait(10L, 10); + myOtherObject.wait(10L); + new Verifications() {{ + myObject.hashCode(); + myOtherObject.wait(); + myObject.wait(anyLong, anyInt); + myOtherObject.wait(anyLong); + }}; + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + @Mock + Object myOtherObject; + + void test() { + myObject.hashCode(); + myOtherObject.wait(); + myObject.wait(10L, 10); + myOtherObject.wait(10L); + verify(myObject).hashCode(); + verify(myOtherObject).wait(); + verify(myObject).wait(anyLong(), anyInt()); + verify(myOtherObject).wait(anyLong()); + } + } + """ + ) + ); + } + + @Test + void whenMultipleVerificationsAndMultipleStatements() { + //language=java + rewriteRun( + java( + """ + import mockit.Verifications; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + myObject.wait(); + new Verifications() {{ + myObject.wait(); + myObject.wait(anyLong, anyInt); + }}; + myObject.wait(1L); + myObject.wait(2L); + new Verifications() {{ + myObject.wait(anyLong); + times = 2; + }}; + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(); + verify(myObject).wait(); + verify(myObject).wait(anyLong(), anyInt()); + myObject.wait(1L); + myObject.wait(2L); + verify(myObject, times(2)).wait(anyLong()); + } + } + """ + ) + ); + } + + @Test + void whenMultipleBlockInSingleVerification() { + //language=java + rewriteRun( + java( + """ + import mockit.Verifications; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + new Verifications() { + { + myObject.wait(); + myObject.wait(anyLong, anyInt); + } + { + myObject.wait(anyLong); + times = 2; + } + }; + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + verify(myObject).wait(); + verify(myObject).wait(anyLong(), anyInt()); + verify(myObject, times(2)).wait(anyLong()); + } + } + """ + ) + ); + } + + @Test + void whenUnsupportedType() { + //language=java + rewriteRun( + java( + """ + import mockit.VerificationsInOrder; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + myObject.wait(1L); + myObject.wait(2L, 1); + new VerificationsInOrder() {{ + myObject.wait(); + myObject.wait(anyLong, anyInt); + }}; + } + } + """, + """ + import mockit.VerificationsInOrder; + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + myObject.wait(1L); + myObject.wait(2L, 1); + new VerificationsInOrder() {{ + myObject.wait(); + myObject.wait(anyLong, anyInt); + }}; + } + } + """ + ) + ); + } + + @Test + void whenWithRedundantThisModifier() { + //language=java + rewriteRun( + java( + """ + import mockit.Verifications; + import mockit.Mocked; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + Object myObject; + + void test() { + new Verifications() {{ + myObject.wait(this.anyLong, this.anyInt); + myObject.wait(anyLong, this.anyInt); + }}; + } + } + """, + """ + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.*; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + Object myObject; + + void test() { + verify(myObject).wait(anyLong(), anyInt()); + verify(myObject).wait(anyLong(), anyInt()); + } + } + """ + ) + ); + } + + @Test + void whenArrayArgumentMatcher() { + //language=java + rewriteRun( + spec -> spec.afterTypeValidationOptions(TypeValidation.builder().methodInvocations(false).build()), + java( + """ + import java.util.List; + + class MyObject { + public String getSomeObject(Object input) { + return "Z"; + } + } + """ + ), + java( + """ + import java.util.ArrayList; + import java.util.List; + + import mockit.Mocked; + import mockit.Verifications; + import mockit.integration.junit5.JMockitExtension; + import org.junit.jupiter.api.extension.ExtendWith; + + @ExtendWith(JMockitExtension.class) + class MyTest { + @Mocked + MyObject myObject; + + void test() { + myObject.getSomeObject(new byte[0]); + myObject.getSomeObject(new int[0]); + myObject.getSomeObject(new Exception[0]); + new Verifications() {{ + myObject.getSomeObject((byte[]) any); + myObject.getSomeObject((int[]) any); + myObject.getSomeObject((Exception[]) any); + }}; + } + } + """, + """ + import java.util.ArrayList; + import java.util.List; + + import static org.mockito.Mockito.any; + import static org.mockito.Mockito.verify; + + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + @ExtendWith(MockitoExtension.class) + class MyTest { + @Mock + MyObject myObject; + + void test() { + myObject.getSomeObject(new byte[0]); + myObject.getSomeObject(new int[0]); + myObject.getSomeObject(new Exception[0]); + verify(myObject).getSomeObject(any(byte[].class)); + verify(myObject).getSomeObject(any(int[].class)); + verify(myObject).getSomeObject(any(Exception[].class)); + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/junit5/AddHamcrestJUnitDependencyTest.java b/src/test/java/org/openrewrite/java/testing/junit5/AddHamcrestJUnitDependencyTest.java new file mode 100644 index 000000000..f4877f12a --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/junit5/AddHamcrestJUnitDependencyTest.java @@ -0,0 +1,132 @@ +/* + * Copyright 2024 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.junit5; + +import org.intellij.lang.annotations.Language; +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; +import static org.openrewrite.maven.Assertions.pomXml; + +class AddHamcrestJUnitDependencyTest implements RewriteTest { + + @Language("xml") + private static final String POM_BEFORE = """ + + org.example + project + 1.0-SNAPSHOT + + + junit + junit + 4.13.2 + test + + + + """; + + @Language("xml") + private static final String POM_AFTER = """ + + org.example + project + 1.0-SNAPSHOT + + + junit + junit + 4.13.2 + test + + + org.hamcrest + hamcrest-junit + 2.0.0.0 + test + + + + """; + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new AddHamcrestJUnitDependency()) + .parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), + "hamcrest", + "junit-4")); + + } + + @Test + @DocumentExample + void shouldAddWhenUsingAssertThat() { + rewriteRun( + //language=java + java( + """ + class FooTest { + void bar() { + org.junit.Assert.assertThat("a", org.hamcrest.Matchers.is("a")); + } + } + """ + ), + pomXml(POM_BEFORE, POM_AFTER) + ); + } + + @Test + void shouldAddWhenUsingAssumeThat() { + rewriteRun( + //language=java + java( + """ + class FooTest { + void bar() { + org.junit.Assume.assumeThat("a", org.hamcrest.Matchers.is("a")); + } + } + """ + ), + pomXml(POM_BEFORE, POM_AFTER) + ); + } + + @Test + void shouldNotAddWhenUsingAssertTrue() { + rewriteRun( + //language=java + java( + """ + class FooTest { + void bar() { + org.junit.Assume.assumeTrue(true); + org.junit.Assert.assertTrue(true); + } + } + """ + ), + pomXml(POM_BEFORE) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/junit5/AddJupiterDependenciesTest.java b/src/test/java/org/openrewrite/java/testing/junit5/AddJupiterDependenciesTest.java new file mode 100644 index 000000000..56a46dc7d --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/junit5/AddJupiterDependenciesTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2024 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.junit5; + +import org.intellij.lang.annotations.Language; +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Issue; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.java.Assertions.*; +import static org.openrewrite.maven.Assertions.pomXml; + +class AddJupiterDependenciesTest implements RewriteTest { + + @Language("java") + private static final String SOME_TEST = """ + import org.junit.jupiter.api.Test; + + class FooTest { + @Test + void bar() { + } + } + """; + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new AddJupiterDependencies()) + .parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "junit-jupiter-api")); + } + + @DocumentExample + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/585") + void addToTestScope() { + rewriteRun( + mavenProject("project", + srcTestJava(java(SOME_TEST)), + pomXml( + //language=xml + """ + + 4.0.0 + org.example + project + 0.0.1 + + """, + spec -> spec.after(pom -> { + assertThat(pom) + .contains("junit-jupiter") + .contains("test"); + return pom; + }) + ) + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/585") + void addToCompileScope() { + rewriteRun( + mavenProject("project", + srcMainJava(java(SOME_TEST)), + pomXml( + //language=xml + """ + + 4.0.0 + org.example + project + 0.0.1 + + """, + spec -> spec.after(pom -> { + assertThat(pom) + .contains("junit-jupiter") + .doesNotContain("test"); + return pom; + }) + ) + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/junit5/AddMissingNestedTest.java b/src/test/java/org/openrewrite/java/testing/junit5/AddMissingNestedTest.java index c8b063887..9aa37dbc8 100644 --- a/src/test/java/org/openrewrite/java/testing/junit5/AddMissingNestedTest.java +++ b/src/test/java/org/openrewrite/java/testing/junit5/AddMissingNestedTest.java @@ -43,23 +43,25 @@ void oneInnerClass() { java( """ import org.junit.jupiter.api.Test; - + public class RootTest { public class InnerTest { @Test - public void test() {} + public void test() { + } } } """, """ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; - + public class RootTest { @Nested public class InnerTest { @Test - public void test() {} + public void test() { + } } } """ @@ -74,20 +76,23 @@ void multipleInnerClasses() { java( """ import org.junit.jupiter.api.Test; - + public class RootTest { public class InnerTest { @Test - public void test() {} + public void test() { + } } - + public class Inner2Test { @Test - public void test() {} - + public void test() { + } + public class InnermostTest { @Test - public void test() {} + public void test() { + } } } } @@ -100,18 +105,21 @@ public class RootTest { @Nested public class InnerTest { @Test - public void test() {} + public void test() { + } } @Nested public class Inner2Test { @Test - public void test() {} + public void test() { + } @Nested public class InnermostTest { @Test - public void test() {} + public void test() { + } } } } @@ -131,11 +139,13 @@ void doesNotAnnotationNonTestInnerClass() { public class RootTest { public class InnerTest { @Test - public void test() {} + public void test() { + } } - + public static class Foo { - public void bar() {} + public void bar() { + } } } """, @@ -147,11 +157,13 @@ public class RootTest { @Nested public class InnerTest { @Test - public void test() {} + public void test() { + } } - + public static class Foo { - public void bar() {} + public void bar() { + } } } """ @@ -170,7 +182,8 @@ void removesStatic() { public class RootTest { public static class InnerTest { @Test - public void test() {} + public void test() { + } } } """, @@ -182,7 +195,8 @@ public class RootTest { @Nested public class InnerTest { @Test - public void test() {} + public void test() { + } } } """ diff --git a/src/test/java/org/openrewrite/java/testing/junit5/AddParameterizedTestAnnotationTest.java b/src/test/java/org/openrewrite/java/testing/junit5/AddParameterizedTestAnnotationTest.java index 5b2e87923..5103fea4d 100644 --- a/src/test/java/org/openrewrite/java/testing/junit5/AddParameterizedTestAnnotationTest.java +++ b/src/test/java/org/openrewrite/java/testing/junit5/AddParameterizedTestAnnotationTest.java @@ -20,10 +20,12 @@ import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.Issue; import org.openrewrite.java.JavaParser; +import org.openrewrite.kotlin.KotlinParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.kotlin.Assertions.kotlin; class AddParameterizedTestAnnotationTest implements RewriteTest { @Override @@ -31,6 +33,8 @@ public void defaults(RecipeSpec spec) { spec .parser(JavaParser.fromJavaVersion() .classpathFromResources(new InMemoryExecutionContext(), "junit-jupiter-api-5.9", "junit-jupiter-params-5.9")) + .parser(KotlinParser.builder() + .classpathFromResources(new InMemoryExecutionContext(), "junit-jupiter-api-5.9", "junit-jupiter-params-5.9")) .recipe(new AddParameterizedTestAnnotation()); } @@ -45,7 +49,7 @@ void replaceTestWithParameterizedTest() { import org.junit.jupiter.api.Test; import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.*; - + class NumbersTest { @Test @ValueSource(ints = {1, 3, 5, -3, 15, Integer.MAX_VALUE}) @@ -58,7 +62,7 @@ void testIsOdd(int number) { import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.*; - + class NumbersTest { @ParameterizedTest @ValueSource(ints = {1, 3, 5, -3, 15, Integer.MAX_VALUE}) @@ -67,6 +71,35 @@ void testIsOdd(int number) { } } """ + ), + //language=kotlin + kotlin( + """ + import org.junit.jupiter.api.Test + import org.junit.jupiter.params.provider.ValueSource + import org.junit.jupiter.api.Assertions.assertTrue + + class NumbersTest { + @Test + @ValueSource(ints = [1, 3, 5, -3, 15, Int.MAX_VALUE]) + fun testIsOdd(number: Int) { + assertTrue(number % 2 != 0) + } + } + """, + """ + import org.junit.jupiter.params.ParameterizedTest + import org.junit.jupiter.params.provider.ValueSource + import org.junit.jupiter.api.Assertions.assertTrue + + class NumbersTest { + @ParameterizedTest + @ValueSource(ints = [1, 3, 5, -3, 15, Int.MAX_VALUE]) + fun testIsOdd(number: Int) { + assertTrue(number % 2 != 0) + } + } + """ ) ); } diff --git a/src/test/java/org/openrewrite/java/testing/junit5/AssertThrowsOnLastStatementTest.java b/src/test/java/org/openrewrite/java/testing/junit5/AssertThrowsOnLastStatementTest.java new file mode 100644 index 000000000..f91702b27 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/junit5/AssertThrowsOnLastStatementTest.java @@ -0,0 +1,283 @@ +/* + * 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.junit5; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Issue; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class AssertThrowsOnLastStatementTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec + .parser(JavaParser.fromJavaVersion() + //.logCompilationWarningsAndErrors(true) + .classpathFromResources(new InMemoryExecutionContext(), "junit-jupiter-api-5.9")) + .recipe(new AssertThrowsOnLastStatement()); + } + + @DocumentExample + @Test + void applyToLastStatementWithDeclaringVariableThreeLines() { + //language=java + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertThrows; + + class MyTest { + + @Test + public void test() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> { + foo(); + System.out.println("foo"); + foo(); + }); + assertEquals("Error message", exception.getMessage()); + } + void foo() { + } + } + """, + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertThrows; + + class MyTest { + + @Test + public void test() { + foo(); + System.out.println("foo"); + Throwable exception = assertThrows(IllegalArgumentException.class, () -> + foo()); + assertEquals("Error message", exception.getMessage()); + } + void foo() { + } + } + """ + ) + ); + } + + @Test + void applyToLastStatementWithDeclaringVariableThreeLinesHasLineBefore() { + //language=java + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertThrows; + + class MyTest { + + @Test + public void test() { + System.out.println("bla"); + Throwable exception = assertThrows(IllegalArgumentException.class, () -> { + foo(); + System.out.println("foo"); + foo(); + }); + assertEquals("Error message", exception.getMessage()); + } + void foo() { + } + } + """, + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertThrows; + + class MyTest { + + @Test + public void test() { + System.out.println("bla"); + foo(); + System.out.println("foo"); + Throwable exception = assertThrows(IllegalArgumentException.class, () -> + foo()); + assertEquals("Error message", exception.getMessage()); + } + void foo() { + } + } + """ + ) + ); + } + + @Test + void applyToLastStatementNoDeclaringVariableTwoLinesNoLinesAfter() { + //language=java + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.assertThrows; + + class MyTest { + + @Test + public void test() { + assertThrows(IllegalArgumentException.class, () -> { + System.out.println("foo"); + foo(); + }); + } + void foo() { + } + } + """, + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.assertThrows; + + class MyTest { + + @Test + public void test() { + System.out.println("foo"); + assertThrows(IllegalArgumentException.class, () -> + foo()); + } + void foo() { + } + } + """ + ) + ); + } + + @Test + void applyToLastStatementHasMessage() { + //language=java + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.assertThrows; + + class MyTest { + + @Test + public void test() { + assertThrows(IllegalArgumentException.class, () -> { + System.out.println("foo"); + foo(); + }, "message"); + } + void foo() { + } + } + """, + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.assertThrows; + + class MyTest { + + @Test + public void test() { + System.out.println("foo"); + assertThrows(IllegalArgumentException.class, () -> { + foo(); + }, "message"); + } + void foo() { + } + } + """ + ) + ); + } + + @Test + void makeNoChangesAsOneLine() { + //language=java + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertThrows; + + class MyTest { + + @Test + public void test() { + Throwable exception = assertThrows(IllegalArgumentException.class, () -> foo()); + assertEquals("Error message", exception.getMessage()); + } + void foo() { + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/618") + void bodyNull() { + //language=java + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.*; + + class MyTest { + + @Test + void test() { + assertThrows(IllegalStateException.class, () -> System.out.println("foo")); + } + + interface InnerInterface { + String createParser(String input); + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/junit5/CleanupAssertionsTest.java b/src/test/java/org/openrewrite/java/testing/junit5/CleanupAssertionsTest.java index 0553fe906..1d4a95426 100644 --- a/src/test/java/org/openrewrite/java/testing/junit5/CleanupAssertionsTest.java +++ b/src/test/java/org/openrewrite/java/testing/junit5/CleanupAssertionsTest.java @@ -18,7 +18,6 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; import org.openrewrite.InMemoryExecutionContext; -import org.openrewrite.config.Environment; import org.openrewrite.java.JavaParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; @@ -44,7 +43,7 @@ void assertTrueComparisonNullToAssertNull() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class ExampleTest { @Test void test() { @@ -105,7 +104,7 @@ void assertFalseNegatedEqualsNullToAssertNull() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class ExampleTest { @Test void test() { @@ -136,7 +135,7 @@ void assertNotEqualsInverted() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class A { class B {} @@ -151,7 +150,7 @@ public void FlippedParams(){ """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class A { class B {} diff --git a/src/test/java/org/openrewrite/java/testing/junit5/CleanupJUnitImportsTest.java b/src/test/java/org/openrewrite/java/testing/junit5/CleanupJUnitImportsTest.java index f59a1cbea..30ec0e109 100644 --- a/src/test/java/org/openrewrite/java/testing/junit5/CleanupJUnitImportsTest.java +++ b/src/test/java/org/openrewrite/java/testing/junit5/CleanupJUnitImportsTest.java @@ -19,10 +19,12 @@ import org.openrewrite.DocumentExample; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.java.JavaParser; +import org.openrewrite.kotlin.KotlinParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.kotlin.Assertions.kotlin; class CleanupJUnitImportsTest implements RewriteTest { @@ -31,14 +33,16 @@ public void defaults(RecipeSpec spec) { spec .parser(JavaParser.fromJavaVersion() .classpathFromResources(new InMemoryExecutionContext(), "junit-4.13")) + .parser(KotlinParser.builder() + .classpathFromResources(new InMemoryExecutionContext(), "junit-4.13")) .recipe(new CleanupJUnitImports()); } @DocumentExample @Test void removesUnusedImport() { - //language=java rewriteRun( + //language=java java( """ import org.junit.Test; @@ -48,14 +52,25 @@ public class MyTest {} """ public class MyTest {} """ + ), + //language=kotlin + kotlin( + """ + import org.junit.Test + + class MyTest {} + """, + """ + class MyTest {} + """ ) ); } @Test void leavesOtherImportsAlone() { - //language=java rewriteRun( + //language=java java( """ import java.util.Arrays; @@ -65,14 +80,25 @@ void leavesOtherImportsAlone() { public class MyTest { } """ + ), + //language=kotlin + kotlin( + """ + import java.util.Arrays + import java.util.Collections + import java.util.HashSet + + class MyTest { + } + """ ) ); } @Test void leavesUsedJUnitImportAlone() { - //language=java rewriteRun( + //language=java java( """ import org.junit.Test; @@ -82,7 +108,19 @@ public class MyTest { public void foo() {} } """ + ), + //language=kotlin + kotlin( + """ + import org.junit.Test + + class MyTest { + @Test + fun foo() {} + } + """ ) ); + } } diff --git a/src/test/java/org/openrewrite/java/testing/junit5/EnclosedToNestedTest.java b/src/test/java/org/openrewrite/java/testing/junit5/EnclosedToNestedTest.java index 7b70ccf0c..1d5cc7f96 100644 --- a/src/test/java/org/openrewrite/java/testing/junit5/EnclosedToNestedTest.java +++ b/src/test/java/org/openrewrite/java/testing/junit5/EnclosedToNestedTest.java @@ -31,7 +31,7 @@ public void defaults(RecipeSpec spec) { spec .parser(JavaParser.fromJavaVersion() .classpathFromResources(new InMemoryExecutionContext(), "junit-4.13")) - .recipe(new EnclosedToNested()); + .recipeFromResources("org.openrewrite.java.testing.junit5.JUnit4to5Migration"); } @DocumentExample @@ -49,21 +49,21 @@ void oneInnerClass() { public class RootTest { public static class InnerTest { @Test - public void test() {} + public void test() { + } } } """, """ - import org.junit.Test; import org.junit.jupiter.api.Nested; + import org.junit.jupiter.api.Test; - \s - \s public class RootTest { @Nested public class InnerTest { @Test - public void test() {} + public void test() { + } } } """ @@ -85,42 +85,46 @@ void multipleInnerClasses() { public class RootTest { public static class InnerTest { @Test - public void test() {} + public void test() { + } } - + public static class Inner2Test { @Test - public void test() {} + public void test() { + } public static class InnermostTest { @Test - public void test() {} + public void test() { + } } } } """, """ - import org.junit.Test; import org.junit.jupiter.api.Nested; + import org.junit.jupiter.api.Test; - \s - \s public class RootTest { @Nested public class InnerTest { @Test - public void test() {} + public void test() { + } } @Nested public class Inner2Test { @Test - public void test() {} + public void test() { + } @Nested public class InnermostTest { @Test - public void test() {} + public void test() { + } } } } @@ -143,21 +147,25 @@ void recognizesTestAnnotationWithArguments() { public class RootTest { public static class InnerTest { @Test(timeout = 10) - public void test() {} + public void test() { + } } } """, """ - import org.junit.Test; import org.junit.jupiter.api.Nested; + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.Timeout; - + import java.util.concurrent.TimeUnit; public class RootTest { @Nested public class InnerTest { - @Test(timeout = 10) - public void test() {} + @Test + @Timeout(value = 10, unit = TimeUnit.MILLISECONDS) + public void test() { + } } } """ @@ -179,29 +187,31 @@ void doesNotAnnotateNonTestInnerClasses() { public class RootTest { public static class InnerTest { @Test - public void test() {} + public void test() { + } } - + public static class Foo { - public void bar() {} + public void bar() { + } } } """, """ - import org.junit.Test; import org.junit.jupiter.api.Nested; - - + import org.junit.jupiter.api.Test; public class RootTest { @Nested public class InnerTest { @Test - public void test() {} + public void test() { + } } - + public static class Foo { - public void bar() {} + public void bar() { + } } } """ diff --git a/src/test/java/org/openrewrite/java/testing/junit5/ExpectedExceptionToAssertThrowsTest.java b/src/test/java/org/openrewrite/java/testing/junit5/ExpectedExceptionToAssertThrowsTest.java index 0b61092b8..c5b4f7835 100644 --- a/src/test/java/org/openrewrite/java/testing/junit5/ExpectedExceptionToAssertThrowsTest.java +++ b/src/test/java/org/openrewrite/java/testing/junit5/ExpectedExceptionToAssertThrowsTest.java @@ -25,6 +25,7 @@ import static org.openrewrite.java.Assertions.java; +@SuppressWarnings({"deprecation", "JUnitMalformedDeclaration", "JUnit3StyleTestMethodInJUnit4Class", "Convert2MethodRef"}) class ExpectedExceptionToAssertThrowsTest implements RewriteTest { @Override @@ -36,7 +37,6 @@ public void defaults(RecipeSpec spec) { .recipe(new ExpectedExceptionToAssertThrows()); } - @DocumentExample @Test void leavesOtherRulesAlone() { //language=java @@ -70,6 +70,7 @@ class MyTest { ); } + @DocumentExample @Test void expectedExceptionRule() { //language=java @@ -105,9 +106,8 @@ class MyTest { @Test public void testEmptyPath() { - Throwable exception = assertThrows(IllegalArgumentException.class, () -> { - foo(); - }); + Throwable exception = assertThrows(IllegalArgumentException.class, () -> + foo()); assertTrue(exception.getMessage().contains("Invalid location: gs://")); } void foo() { @@ -421,4 +421,54 @@ public void expectExceptionUseCases() { ) ); } + + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/563") + void expectedCheckedExceptionThrowsRemoved() { + //language=java + rewriteRun( + java( + """ + import java.io.IOException; + + import org.junit.Rule; + import org.junit.Test; + import org.junit.rules.ExpectedException; + + class MyTest { + + @Rule + ExpectedException thrown = ExpectedException.none(); + + @Test + public void testEmptyPath() throws IOException{ + this.thrown.expect(IOException.class); + foo(); + } + void foo() throws IOException { + throw new IOException(); + } + } + """, + """ + import java.io.IOException; + + import static org.junit.jupiter.api.Assertions.assertThrows; + import org.junit.Test; + + class MyTest { + + @Test + public void testEmptyPath() { + assertThrows(IOException.class, () -> + foo()); + } + void foo() throws IOException { + throw new IOException(); + } + } + """ + ) + ); + } } diff --git a/src/test/java/org/openrewrite/java/testing/junit5/JUnit5BestPracticesTest.java b/src/test/java/org/openrewrite/java/testing/junit5/JUnit5BestPracticesTest.java index 3d1dccc53..412e39e39 100644 --- a/src/test/java/org/openrewrite/java/testing/junit5/JUnit5BestPracticesTest.java +++ b/src/test/java/org/openrewrite/java/testing/junit5/JUnit5BestPracticesTest.java @@ -189,7 +189,7 @@ void somethingElse() { ) ); } - + @Test void changeThrowingRunnableToExecutable() { //language=java diff --git a/src/test/java/org/openrewrite/java/testing/junit5/JUnit5MigrationTest.java b/src/test/java/org/openrewrite/java/testing/junit5/JUnit5MigrationTest.java index 17478e64c..71752bbb3 100644 --- a/src/test/java/org/openrewrite/java/testing/junit5/JUnit5MigrationTest.java +++ b/src/test/java/org/openrewrite/java/testing/junit5/JUnit5MigrationTest.java @@ -55,7 +55,7 @@ void classReference() { java( """ import org.junit.Test; - + public class Sample { void method() { Class c = Test.class; @@ -64,7 +64,7 @@ void method() { """, """ import org.junit.jupiter.api.Test; - + public class Sample { void method() { Class c = Test.class; @@ -85,10 +85,10 @@ void assertThatReceiver() { """ import org.junit.Assert; import org.junit.Test; - + import static java.util.Arrays.asList; import static org.hamcrest.Matchers.containsInAnyOrder; - + public class SampleTest { @SuppressWarnings("ALL") @Test @@ -100,11 +100,11 @@ public void filterShouldRemoveUnusedConfig() { """, """ import org.junit.jupiter.api.Test; - + import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; - + public class SampleTest { @SuppressWarnings("ALL") @Test @@ -178,6 +178,30 @@ void dontExcludeJunit4DependencyfromTestcontainers() { rewriteRun(pomXml(before, before)); } + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/429") + void dontExcludeJunit4DependencyfromTestcontainersJupiter() { + //language=xml + String before = """ + + 4.0.0 + com.example.jackson + test-plugins + 1.0.0 + + + org.testcontainers + junit-jupiter + 1.18.3 + test + + + + """; + // Output identical, but we want to make sure we don't exclude junit4 from testcontainers + rewriteRun(pomXml(before, before)); + } + @Test @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/477") void dontExcludeJunit4DependencyfromSpringBootTestcontainers() { @@ -231,7 +255,7 @@ void assertEqualsWithArrayArgumentToAssertArrayEquals() { java( """ import org.junit.Assert; - + class MyTest { void test() { Assert.assertEquals(new Object[1], new Object[1]); @@ -240,7 +264,7 @@ void test() { """, """ import org.junit.jupiter.api.Assertions; - + class MyTest { void test() { Assertions.assertArrayEquals(new Object[1], new Object[1]); @@ -261,16 +285,16 @@ void migrateInheritedTestBeforeAfterAnnotations() { import org.junit.After; import org.junit.Before; import org.junit.Test; - + public class AbstractTest { @Before public void before() { } - + @After public void after() { } - + @Test public void test() { } @@ -280,16 +304,16 @@ public void test() { import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - + public class AbstractTest { @BeforeEach public void before() { } - + @AfterEach public void after() { } - + @Test public void test() { } @@ -301,10 +325,10 @@ public void test() { public class A extends AbstractTest { public void before() { } - + public void after() { } - + public void test() { } } @@ -313,16 +337,16 @@ public void test() { import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - + public class A extends AbstractTest { @BeforeEach public void before() { } - + @AfterEach public void after() { } - + @Test public void test() { } diff --git a/src/test/java/org/openrewrite/java/testing/junit5/MigrateJUnitTestCaseTest.java b/src/test/java/org/openrewrite/java/testing/junit5/MigrateJUnitTestCaseTest.java index 80084d858..ecf9c1fa2 100644 --- a/src/test/java/org/openrewrite/java/testing/junit5/MigrateJUnitTestCaseTest.java +++ b/src/test/java/org/openrewrite/java/testing/junit5/MigrateJUnitTestCaseTest.java @@ -42,25 +42,25 @@ void convertTestCase() { java( """ import junit.framework.TestCase; - + public class MathTest extends TestCase { protected long value1; protected long value2; - + @Override protected void setUp() { super.setUp(); value1 = 2; value2 = 3; } - + public void testAdd() { setName("primitive test"); long result = value1 + value2; assertEquals(5, result); fail("some Failure message"); } - + @Override protected void tearDown() { super.tearDown(); @@ -73,19 +73,19 @@ protected void tearDown() { import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - + import static org.junit.jupiter.api.Assertions.*; public class MathTest { protected long value1; protected long value2; - + @BeforeEach public void setUp() { value1 = 2; value2 = 3; } - + @Test public void testAdd() { //setName("primitive test"); @@ -93,7 +93,7 @@ public void testAdd() { assertEquals(5, result); fail("some Failure message"); } - + @AfterEach public void tearDown() { value1 = 0; @@ -116,7 +116,7 @@ void convertExtendedTestCase() { public abstract class CTest extends TestCase { @Override public void setUp() {} - + @Override public void tearDown() {} } @@ -125,11 +125,11 @@ public void tearDown() {} package com.abc; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; - + public abstract class CTest { @BeforeEach public void setUp() {} - + @AfterEach public void tearDown() {} } @@ -143,18 +143,18 @@ public void tearDown() {} public class MathTest extends CTest { protected long value1; protected long value2; - + @Override protected void setUp() { value1 = 2; value2 = 3; } - + public void testAdd() { long result = value1 + value2; assertEquals(5, result); } - + @Override protected void tearDown() { value1 = 0; @@ -168,25 +168,25 @@ protected void tearDown() { import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - + import static org.junit.jupiter.api.Assertions.assertEquals; public class MathTest extends CTest { protected long value1; protected long value2; - + @BeforeEach public void setUp() { value1 = 2; value2 = 3; } - + @Test public void testAdd() { long result = value1 + value2; assertEquals(5, result); } - + @AfterEach public void tearDown() { value1 = 0; @@ -205,9 +205,9 @@ void notTestCaseHasTestCaseAssertion() { java( """ import org.junit.Test; - + import static junit.framework.TestCase.assertTrue; - + class AaTest { @Test public void someTest() { @@ -220,9 +220,9 @@ private boolean isSameStuff(String stuff) { """, """ import org.junit.Test; - + import static org.junit.jupiter.api.Assertions.assertTrue; - + class AaTest { @Test public void someTest() { @@ -244,9 +244,9 @@ void notTestCaseHasAssertAssertion() { java( """ import org.junit.Test; - + import static junit.framework.Assert.assertTrue; - + class AaTest { @Test public void someTest() { @@ -259,9 +259,9 @@ private boolean isSameStuff(String stuff) { """, """ import org.junit.Test; - + import static org.junit.jupiter.api.Assertions.assertTrue; - + class AaTest { @Test public void someTest() { @@ -293,4 +293,48 @@ public void testSomeNumberStuff() { ) ); } + + @Test + void avoidDuplicateAnnotations(){ + rewriteRun( + spec -> spec.recipes( + new MigrateJUnitTestCase(), + new UpdateBeforeAfterAnnotations() + ), + //language=java + java( + """ + import junit.framework.TestCase; + import org.junit.After; + import org.junit.Before; + + public class MathTest extends TestCase { + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + } + """, + """ + import org.junit.jupiter.api.AfterEach; + import org.junit.jupiter.api.BeforeEach; + + public class MathTest { + + @BeforeEach + public void setUp() { + } + + @AfterEach + public void tearDown() { + } + } + """ + ) + ); + } } diff --git a/src/test/java/org/openrewrite/java/testing/junit5/RemoveTryCatchFailBlocksTest.java b/src/test/java/org/openrewrite/java/testing/junit5/RemoveTryCatchFailBlocksTest.java index 70afbcac4..0c68d508e 100644 --- a/src/test/java/org/openrewrite/java/testing/junit5/RemoveTryCatchFailBlocksTest.java +++ b/src/test/java/org/openrewrite/java/testing/junit5/RemoveTryCatchFailBlocksTest.java @@ -45,7 +45,7 @@ void removeTryCatchBlock() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test public void testMethod() { @@ -60,7 +60,7 @@ public void testMethod() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test public void testMethod() { @@ -83,7 +83,7 @@ void removeStaticImportFail() { import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.fail; - + class MyTest { @Test public void testMethod() { @@ -98,7 +98,7 @@ public void testMethod() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test public void testMethod() { @@ -120,7 +120,7 @@ void removeTryCatchBlockWithoutMessage() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test public void testMethod() { @@ -135,7 +135,7 @@ public void testMethod() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test public void testMethod() { @@ -157,7 +157,7 @@ void removeTryCatchBlockWithFailString() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test public void testMethod() { @@ -172,7 +172,7 @@ public void testMethod() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test public void testMethod() { @@ -194,7 +194,7 @@ void failMethodArgIsNotGetMessage() { """ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Assertions; - + class MyTest { @Test void aTest() { @@ -204,7 +204,7 @@ void aTest() { Assertions.fail(cleanUpAndReturnMessage()); } } - + String cleanUpAndReturnMessage() { System.out.println("clean up code"); return "Oh no!"; @@ -223,7 +223,7 @@ void doesNotRunWithMultipleCatchBlocks() { """ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Assertions; - + class MyTest { @Test void aTest() { @@ -249,7 +249,7 @@ void catchHasMultipleStatements() { """ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Assertions; - + class MyTest { @Test void aTest() { @@ -275,7 +275,7 @@ void doesNotRunOnTryWithResources() { import org.junit.jupiter.api.Test; import java.io.PrintWriter; import org.junit.jupiter.api.Assertions; - + class MyTest { @Test void aTest() { @@ -299,7 +299,7 @@ void statementsBeforeAndAfterTryBlock() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test public void testMethod() { @@ -320,7 +320,7 @@ public void testMethod() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test public void testMethod() { @@ -348,7 +348,7 @@ void failWithStringThrowableArgs() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test void testMethod() { @@ -363,7 +363,7 @@ void testMethod() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test void testMethod() { @@ -386,7 +386,7 @@ void failWithSupplierStringAsIdentifier() { import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.util.function.Supplier; - + class MyTest { @Test void testMethod() { @@ -412,7 +412,7 @@ void failWithSupplierStringAsLambda() { import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.util.function.Supplier; - + class MyTest { @Test void testMethod() { @@ -436,7 +436,7 @@ void failWithThrowable() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test void testMethod() { @@ -451,7 +451,7 @@ void testMethod() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test void testMethod() { @@ -473,7 +473,7 @@ void multipleTryCatchBlocks() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test public void testMethod() { @@ -493,7 +493,7 @@ public void testMethod() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test public void testMethod() { @@ -518,7 +518,7 @@ void failHasBinaryWithException() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test public void testMethod() { @@ -533,7 +533,7 @@ public void testMethod() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test public void testMethod() { @@ -555,7 +555,7 @@ void failHasBinaryWithMethodCall() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test public void testMethod() { @@ -565,7 +565,7 @@ public void testMethod() { Assertions.fail("The error is: " + anotherMethod()); } } - + public String anotherMethod() { return "anotherMethod"; } @@ -602,7 +602,7 @@ public void testMethod() { import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.io.FileOutputStream; - + class MyTest { @Test public void testMethod() { @@ -625,7 +625,7 @@ void doesNotRunonTryFinally() { """ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - + class MyTest { @Test public void testMethod() { @@ -642,4 +642,52 @@ public void testMethod() { ) ); } + + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/547") + void noChangeOnDirectReturn() { + //language=java + rewriteRun( + java( + """ + import static org.junit.jupiter.api.Assertions.fail; + + class Foo { + String getFoo() { + try { + return "foo"; + } catch (RuntimeException e) { + fail(); + } + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/547") + void noChangeOnNestedReturn() { + //language=java + rewriteRun( + java( + """ + import static org.junit.jupiter.api.Assertions.fail; + + class Foo { + String getBar(boolean b) { + try { + if (b) { + return "bar"; + } + } catch (RuntimeException e) { + fail(); + } + } + } + """ + ) + ); + } } diff --git a/src/test/java/org/openrewrite/java/testing/junit5/UpdateBeforeAfterAnnotationsTest.java b/src/test/java/org/openrewrite/java/testing/junit5/UpdateBeforeAfterAnnotationsTest.java index 47d723d7a..2c71b6107 100644 --- a/src/test/java/org/openrewrite/java/testing/junit5/UpdateBeforeAfterAnnotationsTest.java +++ b/src/test/java/org/openrewrite/java/testing/junit5/UpdateBeforeAfterAnnotationsTest.java @@ -21,10 +21,12 @@ import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.Issue; import org.openrewrite.java.JavaParser; +import org.openrewrite.kotlin.KotlinParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.kotlin.Assertions.kotlin; @SuppressWarnings("JUnitMalformedDeclaration") class UpdateBeforeAfterAnnotationsTest implements RewriteTest { @@ -34,6 +36,8 @@ public void defaults(RecipeSpec spec) { spec .parser(JavaParser.fromJavaVersion() .classpathFromResources(new InMemoryExecutionContext(), "junit-4.13")) + .parser(KotlinParser.builder() + .classpathFromResources(new InMemoryExecutionContext(), "junit-4.13")) .recipe(new UpdateBeforeAfterAnnotations()); } @@ -45,9 +49,8 @@ void beforeToBeforeEach() { java( """ import org.junit.Before; - + class Test { - @Before void before() { } @@ -55,14 +58,36 @@ void before() { """, """ import org.junit.jupiter.api.BeforeEach; - + class Test { - @BeforeEach void before() { } } """ + ), + //language=kotlin + kotlin( + """ + import org.junit.Before + + class Test { + + @Before + fun before() { + } + } + """, + """ + import org.junit.jupiter.api.BeforeEach + + class Test { + + @BeforeEach + fun before() { + } + } + """ ) ); } @@ -185,8 +210,8 @@ void before() { } @Test - @Disabled("Issue #59") - void beforeMethodOverridesPublicAbstract() { + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/59") + void retainPublicModifierOnOverriddenMethod() { //language=java rewriteRun( diff --git a/src/test/java/org/openrewrite/java/testing/junit5/UpdateTestAnnotationTest.java b/src/test/java/org/openrewrite/java/testing/junit5/UpdateTestAnnotationTest.java index 22228547c..b6e3c950e 100644 --- a/src/test/java/org/openrewrite/java/testing/junit5/UpdateTestAnnotationTest.java +++ b/src/test/java/org/openrewrite/java/testing/junit5/UpdateTestAnnotationTest.java @@ -43,9 +43,9 @@ void expectedNoneToAssertDoesNotThrow() { java( """ import org.junit.Test; - + public class MyTest { - + @Test(expected = Test.None.class) public void test_printLine() { int arr = new int[]{0}[0]; @@ -54,11 +54,11 @@ public void test_printLine() { """, """ import org.junit.jupiter.api.Test; - + import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - + public class MyTest { - + @Test public void test_printLine() { assertDoesNotThrow(() -> { @@ -78,9 +78,9 @@ void assertThrowsSingleLine() { java( """ import org.junit.Test; - + public class MyTest { - + @Test(expected = IllegalArgumentException.class) public void test() { throw new IllegalArgumentException("boom"); @@ -89,11 +89,11 @@ public void test() { """, """ import org.junit.jupiter.api.Test; - + import static org.junit.jupiter.api.Assertions.assertThrows; - + public class MyTest { - + @Test public void test() { assertThrows(IllegalArgumentException.class, () -> { @@ -106,6 +106,46 @@ public void test() { ); } + @Test + void assertThrowsSingleLineInlined() { + //language=java + rewriteRun( + java( + """ + import org.junit.Test; + + class MyTest { + + @Test(expected = IllegalArgumentException.class) + public void test() { + foo(); + } + private void foo() { + throw new IllegalArgumentException("boom"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.assertThrows; + + class MyTest { + + @Test + public void test() { + assertThrows(IllegalArgumentException.class, () -> + foo()); + } + private void foo() { + throw new IllegalArgumentException("boom"); + } + } + """ + ) + ); + } + @SuppressWarnings("ConstantConditions") @Test void assertThrowsSingleStatement() { @@ -113,23 +153,23 @@ void assertThrowsSingleStatement() { rewriteRun( java( """ - import org.junit.Test; - - public class MyTest { - - @Test(expected = IndexOutOfBoundsException.class) - public void test() { - int arr = new int[]{}[0]; - } + import org.junit.Test; + + public class MyTest { + + @Test(expected = IndexOutOfBoundsException.class) + public void test() { + int arr = new int[]{}[0]; } - """, + } + """, """ import org.junit.jupiter.api.Test; - + import static org.junit.jupiter.api.Assertions.assertThrows; - + public class MyTest { - + @Test public void test() { assertThrows(IndexOutOfBoundsException.class, () -> { @@ -149,9 +189,9 @@ void assertThrowsMultiLine() { java( """ import org.junit.Test; - + public class MyTest { - + @Test(expected = IllegalArgumentException.class) public void test() { String foo = "foo"; @@ -161,11 +201,11 @@ public void test() { """, """ import org.junit.jupiter.api.Test; - + import static org.junit.jupiter.api.Assertions.assertThrows; - + public class MyTest { - + @Test public void test() { assertThrows(IllegalArgumentException.class, () -> { @@ -186,9 +226,9 @@ void noTestAnnotationValues() { java( """ import org.junit.Test; - + public class MyTest { - + @Test public void test() { } @@ -196,9 +236,9 @@ public void test() { """, """ import org.junit.jupiter.api.Test; - + public class MyTest { - + @Test public void test() { } @@ -224,20 +264,20 @@ void preservesComments() { """ import org.junit.Test; import org.openrewrite.Issue; - + public class MyTest { - + // some comments @Issue("some issue") @Test public void test() { } - + // some comments @Test public void test1() { } - + @Test // some comments public void test2() { @@ -247,20 +287,20 @@ public void test2() { """ import org.junit.jupiter.api.Test; import org.openrewrite.Issue; - + public class MyTest { - + // some comments @Issue("some issue") @Test public void test() { } - + // some comments @Test public void test1() { } - + @Test // some comments public void test2() { @@ -279,9 +319,9 @@ void annotationWithTimeout() { java( """ import org.junit.Test; - + public class MyTest { - + @Test(timeout = 500) public void test() { } @@ -290,11 +330,11 @@ public void test() { """ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; - + import java.util.concurrent.TimeUnit; - + public class MyTest { - + @Test @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) public void test() { @@ -323,9 +363,9 @@ public MyException(String message) { """ import com.abc.MyException; import org.junit.Test; - + public class MyTest { - + @Test(expected = MyException.class) public void test() { throw new MyException("my exception"); @@ -335,11 +375,11 @@ public void test() { """ import com.abc.MyException; import org.junit.jupiter.api.Test; - + import static org.junit.jupiter.api.Assertions.assertThrows; - + public class MyTest { - + @Test public void test() { assertThrows(MyException.class, () -> { @@ -359,9 +399,9 @@ void annotationWithTimeoutAndException() { java( """ import org.junit.Test; - + public class MyTest { - + @Test(expected = IllegalArgumentException.class, timeout = 500) public void test() { throw new IllegalArgumentException("boom"); @@ -371,13 +411,13 @@ public void test() { """ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; - + import java.util.concurrent.TimeUnit; - + import static org.junit.jupiter.api.Assertions.assertThrows; - + public class MyTest { - + @Test @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) public void test() { @@ -398,7 +438,7 @@ void preservesVisibilityOnTestMethodThatIsAnOverride() { java( """ package com.test; - + public interface Foo { void foo(); } @@ -407,11 +447,11 @@ public interface Foo { java( """ package com.test; - + import org.junit.Test; - + public class FooTest implements Foo { - + @Test public void foo() { } @@ -419,11 +459,11 @@ public void foo() { """, """ package com.test; - + import org.junit.jupiter.api.Test; - + public class FooTest implements Foo { - + @Test public void foo() { } @@ -447,7 +487,7 @@ public class MyTest { """, """ import org.junit.jupiter.api.Test; - + public class MyTest { Object o = Test.class; } @@ -463,7 +503,7 @@ void usedInJavadoc() { java( """ import org.junit.Test; - + /** @see org.junit.Test */ public class MyTest { @Test @@ -473,7 +513,7 @@ public void test() { """, """ import org.junit.jupiter.api.Test; - + /** @see org.junit.jupiter.api.Test */ public class MyTest { @Test @@ -499,7 +539,7 @@ public void feature1() { """, """ import org.junit.jupiter.api.Test; - + public class MyTest { @org.junit.jupiter.api.Test public void feature1() { @@ -521,7 +561,7 @@ public class MyTest { @org.junit.Test public void feature1() { } - + @Test public void feature2() { } @@ -529,12 +569,12 @@ public void feature2() { """, """ import org.junit.jupiter.api.Test; - + public class MyTest { @org.junit.jupiter.api.Test public void feature1() { } - + @Test public void feature2() { } @@ -543,4 +583,55 @@ public void feature2() { ) ); } + + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/563") + void removeThrowsCheckedException() { + //language=java + rewriteRun( + java( + """ + import org.junit.Test; + import java.io.IOException; + + public class MyTest { + + @Test(expected = IOException.class) + public void testWithThrows() throws IOException { + foo(); + // Second call shows why we wrap the entire method body in the lambda + foo(); + } + + void foo() throws IOException { + throw new IOException(); + } + } + """, + """ + import org.junit.jupiter.api.Test; + + import java.io.IOException; + + import static org.junit.jupiter.api.Assertions.assertThrows; + + public class MyTest { + + @Test + public void testWithThrows() { + assertThrows(IOException.class, () -> { + foo(); + // Second call shows why we wrap the entire method body in the lambda + foo(); + }); + } + + void foo() throws IOException { + throw new IOException(); + } + } + """ + ) + ); + } } diff --git a/src/test/java/org/openrewrite/java/testing/junit5/UseAssertSameTest.java b/src/test/java/org/openrewrite/java/testing/junit5/UseAssertSameTest.java new file mode 100644 index 000000000..d68d05f27 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/junit5/UseAssertSameTest.java @@ -0,0 +1,192 @@ +/* + * Copyright 2024 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.junit5; + +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 UseAssertSameTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec + .parser(JavaParser.fromJavaVersion() + .classpathFromResources(new InMemoryExecutionContext(), "junit-jupiter-api-5.9")) + .recipe(new UseAssertSame()); + } + + @DocumentExample + @Test + void assertSameForSimpleBooleanComparison() { + //language=java + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.assertTrue; + + class MyTest { + + @Test + public void test() { + String number = "thirty-six"; + String otherNumber = number; + assertTrue(number == otherNumber); + } + } + """, + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.assertSame; + + class MyTest { + + @Test + public void test() { + String number = "thirty-six"; + String otherNumber = number; + assertSame(number, otherNumber); + } + } + """ + ) + ); + } + + @Test + void usingStringMessage() { + //language=java + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.assertTrue; + + class MyTest { + + @Test + public void test() { + String number = "thirty-six"; + String otherNumber = number; + assertTrue(number == otherNumber, "Something is not right"); + } + } + """, + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.assertSame; + + class MyTest { + + @Test + public void test() { + String number = "thirty-six"; + String otherNumber = number; + assertSame(number, otherNumber, "Something is not right"); + } + } + """ + ) + ); + } + + @Test + void assertFalse() { + //language=java + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.assertFalse; + + class MyTest { + + @Test + public void test() { + String number = "thirty-six"; + String otherNumber = "thirty-seven"; + assertFalse(number == otherNumber); + } + } + """, + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.assertNotSame; + + class MyTest { + + @Test + public void test() { + String number = "thirty-six"; + String otherNumber = "thirty-seven"; + assertNotSame(number, otherNumber); + } + } + """ + ) + ); + } + + @Test + void notEqual() { + //language=java + rewriteRun( + java( + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.assertTrue; + + class MyTest { + + @Test + public void test() { + String number = "thirty-six"; + String otherNumber = "thirty-seven"; + assertTrue(number != otherNumber); + } + } + """, + """ + import org.junit.jupiter.api.Test; + + import static org.junit.jupiter.api.Assertions.assertNotSame; + + class MyTest { + + @Test + public void test() { + String number = "thirty-six"; + String otherNumber = "thirty-seven"; + assertNotSame(number, otherNumber); + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/junit5/UseXMLUnitLegacyTest.java b/src/test/java/org/openrewrite/java/testing/junit5/UseXMLUnitLegacyTest.java index 902f8fa99..5cefde039 100644 --- a/src/test/java/org/openrewrite/java/testing/junit5/UseXMLUnitLegacyTest.java +++ b/src/test/java/org/openrewrite/java/testing/junit5/UseXMLUnitLegacyTest.java @@ -16,7 +16,6 @@ package org.openrewrite.java.testing.junit5; import org.junit.jupiter.api.Test; -import org.openrewrite.config.Environment; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; @@ -27,10 +26,7 @@ class UseXMLUnitLegacyTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { spec - .recipe(Environment.builder() - .scanRuntimeClasspath() - .build() - .activateRecipes("org.openrewrite.java.testing.junit5.UseXMLUnitLegacy")); + .recipeFromResources("org.openrewrite.java.testing.junit5.UseXMLUnitLegacy"); } @Test diff --git a/src/test/java/org/openrewrite/java/testing/mockito/CleanupMockitoImportsTest.java b/src/test/java/org/openrewrite/java/testing/mockito/CleanupMockitoImportsTest.java index 94ad05d85..744ce019c 100755 --- a/src/test/java/org/openrewrite/java/testing/mockito/CleanupMockitoImportsTest.java +++ b/src/test/java/org/openrewrite/java/testing/mockito/CleanupMockitoImportsTest.java @@ -19,10 +19,12 @@ import org.openrewrite.DocumentExample; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.java.JavaParser; +import org.openrewrite.kotlin.KotlinParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.kotlin.Assertions.kotlin; class CleanupMockitoImportsTest implements RewriteTest { @@ -31,6 +33,7 @@ public void defaults(RecipeSpec spec) { spec .parser(JavaParser.fromJavaVersion() .classpathFromResources(new InMemoryExecutionContext(), "mockito-all-1.10")) + .parser(KotlinParser.builder().classpath("mockito-core", "mockito-kotlin")) .recipe(new CleanupMockitoImports()); } @@ -43,12 +46,12 @@ void removesUnusedMockitoImport() { """ import org.mockito.Mock; import java.util.Arrays; - + public class MyTest {} """, """ import java.util.Arrays; - + public class MyTest {} """ ) @@ -65,7 +68,7 @@ void leavesOtherImportsAlone() { import java.util.Collections; import java.util.HashSet; import java.util.List; - + public class MyTest {} """ ) @@ -85,7 +88,7 @@ void doNotRemoveImportsPossiblyAssociatedWithAnUntypedMockitoMethod() { class MyObjectTest { MyObject myObject; MyMockClass myMock; - + void test() { when(myObject.getSomeField()).thenReturn("testValue"); given(myObject.getSomeField()).willReturn("testValue"); @@ -116,7 +119,7 @@ void doNotRemoveImportsAssociatedWithAnUntypedMockitoMethodMixed() { class MyObjectTest { MyObject myObject; MyMockClass myMock; - + void test() { when(myObject.getSomeField()).thenReturn("testValue"); given(myObject.getSomeField()).willReturn("testValue"); @@ -148,7 +151,7 @@ void doNotRemoveImportsAssociatedWithATypedMockitoMethodMixed() { class MyObjectTest { MyObject myObject; MyMockClass myMock; - + void test() { when(myObject.getSomeField()).thenReturn("testValue"); given(myObject.getSomeField()).willReturn("testValue"); @@ -178,7 +181,7 @@ void doNotRemoveStartImportsPossiblyAssociatedWithAnUntypedMockitoMethod() { class MyObjectTest { MyObject myObject; - + void test() { when(myObject.getSomeField()).thenReturn("testValue"); } @@ -212,7 +215,7 @@ class MyObject { class MyObjectTest { @Mock MyObject myObject; - + void test() { when(myObject.getSomeField()).thenReturn("testValue"); } @@ -226,7 +229,7 @@ void test() { class MyObjectTest { @Mock MyObject myObject; - + void test() { when(myObject.getSomeField()).thenReturn("testValue"); } @@ -243,16 +246,16 @@ void preserveStarImports() { java( """ package mockito.example; - + import java.util.List; - + import static org.mockito.Mockito.*; - + public class MockitoArgumentMatchersTest { static class Foo { boolean bool(String str, int i, Object obj) { return false; } } - + public void usesMatchers() { Foo mockFoo = mock(Foo.class); when(mockFoo.bool(anyString(), anyInt(), any(Object.class))).thenReturn(true); @@ -270,7 +273,7 @@ void removeUnusedStarImports() { java( """ import static org.mockito.Mockito.*; - + public class MockitoArgumentMatchersTest { } """, @@ -281,4 +284,28 @@ public class MockitoArgumentMatchersTest { ) ); } + + @Test + void handleKotlinImportsCorrectly() { + rewriteRun( + //language=kotlin + kotlin( + """ + import org.mockito.kotlin.times + class Foo { + fun bar() { + org.mockito.Mockito.mock(Foo::class.java) + } + } + """, + """ + class Foo { + fun bar() { + org.mockito.Mockito.mock(Foo::class.java) + } + } + """ + ) + ); + } } diff --git a/src/test/java/org/openrewrite/java/testing/mockito/MockitoInlineToCoreTest.java b/src/test/java/org/openrewrite/java/testing/mockito/MockitoInlineToCoreTest.java index a7bdb1998..7e68ccb00 100644 --- a/src/test/java/org/openrewrite/java/testing/mockito/MockitoInlineToCoreTest.java +++ b/src/test/java/org/openrewrite/java/testing/mockito/MockitoInlineToCoreTest.java @@ -16,6 +16,7 @@ package org.openrewrite.java.testing.mockito; import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; @@ -29,6 +30,7 @@ public void defaults(RecipeSpec spec) { spec.recipeFromResources("org.openrewrite.java.testing.mockito.Mockito1to5Migration"); } + @DocumentExample @Test void inlineToCore() { rewriteRun( @@ -50,25 +52,22 @@ void inlineToCore() { """, - sourceSpecs -> sourceSpecs.after(after -> { - String version = Pattern.compile("(5.+)").matcher(after).results().reduce((a, b) -> b).orElseThrow().group(1); - return """ - - 4.0.0 - com.example - demo - 0.0.1-SNAPSHOT - - - org.mockito - mockito-core - %s - test - - - - """.formatted(version); - }) + spec -> spec.after(after -> """ + + 4.0.0 + com.example + demo + 0.0.1-SNAPSHOT + + + org.mockito + mockito-core + %s + test + + + + """.formatted(Pattern.compile("(5.+)").matcher(after).results().findFirst().orElseThrow().group(1))) ) ); } @@ -100,25 +99,77 @@ void noDuplicates() { """, - sourceSpecs -> sourceSpecs.after(after -> { - String version = Pattern.compile("(5.+)").matcher(after).results().reduce((a, b) -> b).orElseThrow().group(1); - return """ - - 4.0.0 - com.example - demo - 0.0.1-SNAPSHOT - - - org.mockito - mockito-core - %s - test - - - - """.formatted(version); - }) + spec -> spec.after(after -> """ + + 4.0.0 + com.example + demo + 0.0.1-SNAPSHOT + + + org.mockito + mockito-core + %s + test + + + + """.formatted(Pattern.compile("(5.+)").matcher(after).results().findFirst().orElseThrow().group(1))) + ) + ); + } + + @Test + void shouldUpdateByteBuddy() { + rewriteRun( + //language=xml + pomXml( + """ + + 4.0.0 + com.example + demo + 0.0.1-SNAPSHOT + + + org.mockito + mockito-core + 5.13.0 + test + + + net.bytebuddy + byte-buddy + 1.12.19 + test + + + + """, + spec -> spec.after(after -> """ + + 4.0.0 + com.example + demo + 0.0.1-SNAPSHOT + + + org.mockito + mockito-core + %s + test + + + net.bytebuddy + byte-buddy + %s + test + + + + """.formatted( + Pattern.compile("(5.+)").matcher(after).results().findFirst().orElseThrow().group(1), + Pattern.compile("(1.15.+)").matcher(after).results().findFirst().orElseThrow().group(1))) ) ); } diff --git a/src/test/java/org/openrewrite/java/testing/mockito/MockitoJUnitToMockitoExtensionTest.java b/src/test/java/org/openrewrite/java/testing/mockito/MockitoJUnitToMockitoExtensionTest.java index 0b8d71722..03f3017f2 100644 --- a/src/test/java/org/openrewrite/java/testing/mockito/MockitoJUnitToMockitoExtensionTest.java +++ b/src/test/java/org/openrewrite/java/testing/mockito/MockitoJUnitToMockitoExtensionTest.java @@ -48,12 +48,12 @@ void leavesOtherRulesAlone() { import org.mockito.Mock; import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoJUnit; - + class MyTest { - + @Rule TemporaryFolder tempDir = new TemporaryFolder(); - + @Rule MockitoRule mockitoRule = MockitoJUnit.rule(); } @@ -64,10 +64,13 @@ class MyTest { import org.junit.rules.TemporaryFolder; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; + @ExtendWith(MockitoExtension.class) + @MockitoSettings(strictness = Strictness.WARN) class MyTest { - + @Rule TemporaryFolder tempDir = new TemporaryFolder(); } @@ -89,10 +92,10 @@ void leavesOtherAnnotationsAlone() { import org.mockito.Mock; import org.mockito.junit.MockitoRule; import org.mockito.junit.MockitoJUnit; - + @FixMethodOrder(MethodSorters.NAME_ASCENDING) class MyTest { - + @Rule MockitoRule mockitoRule = MockitoJUnit.rule(); } @@ -103,9 +106,12 @@ class MyTest { import org.junit.runners.MethodSorters; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; + @ExtendWith(MockitoExtension.class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) + @MockitoSettings(strictness = Strictness.WARN) class MyTest { } """ @@ -121,21 +127,21 @@ void refactorMockitoRule() { java( """ import java.util.List; - + import org.junit.Rule; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.mockito.quality.Strictness; - + class MyTest { - + @Rule MockitoRule mockitoRule = MockitoJUnit.rule(); - + @Mock private List list; - + public void exampleTest() { mockitoRule.strictness(Strictness.LENIENT); list.add(100); @@ -144,17 +150,20 @@ public void exampleTest() { """, """ import java.util.List; - + import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; + @ExtendWith(MockitoExtension.class) + @MockitoSettings(strictness = Strictness.LENIENT) class MyTest { - + @Mock private List list; - + public void exampleTest() { list.add(100); } @@ -172,21 +181,21 @@ void refactorMockitoTestRule() { java( """ import java.util.List; - + import org.junit.Rule; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoTestRule; import org.mockito.quality.Strictness; - + class MyTest { - + @Rule MockitoTestRule mockitoTestRule = MockitoJUnit.rule(); - + @Mock private List list; - + public void exampleTest() { mockitoTestRule.strictness(Strictness.LENIENT); list.add(100); @@ -195,17 +204,20 @@ public void exampleTest() { """, """ import java.util.List; - + import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; + @ExtendWith(MockitoExtension.class) + @MockitoSettings(strictness = Strictness.LENIENT) class MyTest { - + @Mock private List list; - + public void exampleTest() { list.add(100); } @@ -229,22 +241,22 @@ void onlyRefactorMockitoRule() { import org.mockito.junit.MockitoTestRule; import org.mockito.junit.VerificationCollector; import org.mockito.quality.Strictness; - + import java.util.List; - + import static org.mockito.Mockito.verify; - + class MyTest { - + @Rule VerificationCollector verificationCollectorRule = MockitoJUnit.collector(); - + @Rule MockitoTestRule mockitoTestRule = MockitoJUnit.rule(); - + @Mock private List list; - + @Test public void exampleTest() { verify(list).add(100); @@ -260,20 +272,23 @@ public void exampleTest() { import org.mockito.junit.MockitoJUnit; import org.mockito.junit.VerificationCollector; import org.mockito.junit.jupiter.MockitoExtension; - + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; + import java.util.List; - + import static org.mockito.Mockito.verify; - + @ExtendWith(MockitoExtension.class) + @MockitoSettings(strictness = Strictness.WARN) class MyTest { - + @Rule VerificationCollector verificationCollectorRule = MockitoJUnit.collector(); - + @Mock private List list; - + @Test public void exampleTest() { verify(list).add(100); @@ -293,20 +308,20 @@ void unchangedMockitoCollectorRule() { java( """ import java.util.List; - + import org.junit.Rule; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.VerificationCollector; - + class MyTest { - + @Rule VerificationCollector verificationCollectorRule = MockitoJUnit.collector(); - + @Mock private List list; - + public void exampleTest() { list.add(100); verificationCollectorRule.collectAndReport(); @@ -325,25 +340,25 @@ void unchangedMockitoCollectorDeclaredInMethod() { java( """ import java.util.List; - + import org.mockito.Mock; import org.mockito.exceptions.base.MockitoAssertionError; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.VerificationCollector; - + import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; - + class MyTest { - + public void unsupported() { VerificationCollector collector = MockitoJUnit.collector().assertLazily(); - + List mockList = mock(List.class); verify(mockList).add("one"); verify(mockList).clear(); - + try { collector.collectAndReport(); } catch (MockitoAssertionError error) { @@ -374,20 +389,20 @@ void leaveMockitoJUnitRunnerAlone() { import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoTestRule; import org.mockito.runners.MockitoJUnitRunner; - + import java.util.List; - + import static org.mockito.Mockito.verify; - + @RunWith(MockitoJUnitRunner.class) class MyTest { - + @Rule MockitoTestRule mockitoTestRule = MockitoJUnit.rule(); - + @Mock private List list; - + @Test public void exampleTest() { verify(list).add(100); @@ -399,17 +414,17 @@ public void exampleTest() { import org.junit.Test; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; - + import java.util.List; - + import static org.mockito.Mockito.verify; - + @RunWith(MockitoJUnitRunner.class) class MyTest { - + @Mock private List list; - + @Test public void exampleTest() { verify(list).add(100); @@ -434,20 +449,20 @@ void leaveExtendWithAlone() { import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoTestRule; - + import java.util.List; - + import static org.mockito.Mockito.verify; - + @ExtendWith(MockitoExtension.class) class MyTest { - + @Rule MockitoTestRule mockitoTestRule = MockitoJUnit.rule(); - + @Mock private List list; - + @Test public void exampleTest() { verify(list).add(100); @@ -459,17 +474,17 @@ public void exampleTest() { import org.junit.Test; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - + import java.util.List; - + import static org.mockito.Mockito.verify; - + @ExtendWith(MockitoExtension.class) class MyTest { - + @Mock private List list; - + @Test public void exampleTest() { verify(list).add(100); @@ -479,4 +494,315 @@ public void exampleTest() { ) ); } + + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/623") + @Test + void silentRuleAddMockitoSettings() { + //language=java + rewriteRun( + java( + """ + import org.junit.Rule; + import org.junit.Test; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnit; + import org.mockito.junit.MockitoRule; + + import java.util.List; + + import static org.mockito.Mockito.when; + + public class MyTest { + + @Rule + public MockitoRule rule = MockitoJUnit.rule().silent(); + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """, + """ + import org.junit.Test; + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; + + import java.util.List; + + import static org.mockito.Mockito.when; + + @ExtendWith(MockitoExtension.class) + @MockitoSettings(strictness = Strictness.LENIENT) + public class MyTest { + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/623") + @Test + void warnStrictnessRuleAddMockitoSettings() { + //language=java + rewriteRun( + java( + """ + import org.junit.Rule; + import org.junit.Test; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnit; + import org.mockito.junit.MockitoRule; + import org.mockito.quality.Strictness; + + import java.util.List; + + import static org.mockito.Mockito.when; + + public class MyTest { + + @Rule + public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.WARN); + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """, + """ + import org.junit.Test; + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; + + import java.util.List; + + import static org.mockito.Mockito.when; + + @ExtendWith(MockitoExtension.class) + @MockitoSettings(strictness = Strictness.WARN) + public class MyTest { + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/623") + @Test + void warnStrictnessRuleAddMockitoSettingsWithStaticImport() { + //language=java + rewriteRun( + java( + """ + import org.junit.Rule; + import org.junit.Test; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnit; + import org.mockito.junit.MockitoRule; + + import java.util.List; + + import static org.mockito.Mockito.when; + import static org.mockito.quality.Strictness.WARN; + + public class MyTest { + + @Rule + public MockitoRule rule = MockitoJUnit.rule().strictness(WARN); + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """, + """ + import org.junit.Test; + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; + + import java.util.List; + + import static org.mockito.Mockito.when; + + @ExtendWith(MockitoExtension.class) + @MockitoSettings(strictness = Strictness.WARN) + public class MyTest { + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/623") + @Test + void lenientStrictnessRuleAddMockitoSettings() { + //language=java + rewriteRun( + java( + """ + import org.junit.Rule; + import org.junit.Test; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnit; + import org.mockito.junit.MockitoRule; + import org.mockito.quality.Strictness; + + import java.util.List; + + import static org.mockito.Mockito.when; + + public class MyTest { + + @Rule + public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.LENIENT); + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """, + """ + import org.junit.Test; + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; + + import java.util.List; + + import static org.mockito.Mockito.when; + + @ExtendWith(MockitoExtension.class) + @MockitoSettings(strictness = Strictness.LENIENT) + public class MyTest { + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/623") + @Test + void strictRuleDoNotAddMockitoSettings() { + //language=java + rewriteRun( + java( + """ + import org.junit.Rule; + import org.junit.Test; + import org.mockito.Mock; + import org.mockito.junit.MockitoJUnit; + import org.mockito.junit.MockitoRule; + import org.mockito.quality.Strictness; + + import java.util.List; + + import static org.mockito.Mockito.when; + + public class MyTest { + + @Rule + public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """, + """ + import org.junit.Test; + import org.junit.jupiter.api.extension.ExtendWith; + import org.mockito.Mock; + import org.mockito.junit.jupiter.MockitoExtension; + + import java.util.List; + + import static org.mockito.Mockito.when; + + @ExtendWith(MockitoExtension.class) + public class MyTest { + + @Mock + private List mockList; + + @Test + public void testing() { + when(mockList.add("one")).thenReturn(true); // this won't get called + System.out.println("Hello world!"); + } + } + """ + ) + ); + } } diff --git a/src/test/java/org/openrewrite/java/testing/mockito/MockitoWhenOnStaticToMockStaticTest.java b/src/test/java/org/openrewrite/java/testing/mockito/MockitoWhenOnStaticToMockStaticTest.java new file mode 100644 index 000000000..a7eb4b724 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/mockito/MockitoWhenOnStaticToMockStaticTest.java @@ -0,0 +1,150 @@ +/* + * Copyright 2024 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.DocumentExample; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.SourceSpec; +import org.openrewrite.test.TypeValidation; + +import static org.openrewrite.java.Assertions.java; + +class MockitoWhenOnStaticToMockStaticTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new MockitoWhenOnStaticToMockStatic()) + .parser(JavaParser.fromJavaVersion() + .classpathFromResources(new InMemoryExecutionContext(), + "junit-4.13", + "mockito-core-3.12", + "mockito-junit-jupiter-3.12" + )); + } + + @DocumentExample + @Test + void shouldRefactorMockito_When() { + //language=java + rewriteRun( + spec -> spec.afterTypeValidationOptions(TypeValidation.builder().identifiers(false).build()), + java( + """ + package com.foo; + public class A { + public static Integer getNumber() { + return 42; + } + } + """, + SourceSpec::skip + ), + java( + """ + import com.foo.A; + + import static org.junit.Assert.assertEquals; + import static org.mockito.Mockito.*; + + class Test { + void test() { + when(A.getNumber()).thenReturn(-1); + assertEquals(A.getNumber(), -1); + } + } + """, + """ + import com.foo.A; + import org.mockito.MockedStatic; + + import static org.junit.Assert.assertEquals; + import static org.mockito.Mockito.*; + + class Test { + void test() { + try (MockedStatic mockA = mockStatic(A.class)) { + mockA.when(A.getNumber()).thenReturn(-1); + assertEquals(A.getNumber(), -1); + } + } + } + """ + ) + ); + } + + @Test + void shouldHandleMultipleStaticMocks() { + //language=java + rewriteRun( + spec -> spec.afterTypeValidationOptions(TypeValidation.builder().identifiers(false).build()), + java( + """ + package com.foo; + public class A { + public static Integer getNumber() { + return 42; + } + } + """, + SourceSpec::skip + ), + java( + """ + import com.foo.A; + + import static org.junit.Assert.assertEquals; + import static org.mockito.Mockito.*; + + class Test { + void test() { + when(A.getNumber()).thenReturn(-1); + assertEquals(A.getNumber(), -1); + + when(A.getNumber()).thenReturn(-2); + assertEquals(A.getNumber(), -2); + } + } + """, + """ + import com.foo.A; + import org.mockito.MockedStatic; + + import static org.junit.Assert.assertEquals; + import static org.mockito.Mockito.*; + + class Test { + void test() { + try (MockedStatic mockA = mockStatic(A.class)) { + mockA.when(A.getNumber()).thenReturn(-1); + assertEquals(A.getNumber(), -1); + + try (MockedStatic mockA1 = mockStatic(A.class)) { + mockA1.when(A.getNumber()).thenReturn(-2); + assertEquals(A.getNumber(), -2); + } + } + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/mockito/PowerMockitoMockStaticToMockitoTest.java b/src/test/java/org/openrewrite/java/testing/mockito/PowerMockitoMockStaticToMockitoTest.java index 64d8f7472..3a3225ad4 100644 --- a/src/test/java/org/openrewrite/java/testing/mockito/PowerMockitoMockStaticToMockitoTest.java +++ b/src/test/java/org/openrewrite/java/testing/mockito/PowerMockitoMockStaticToMockitoTest.java @@ -22,6 +22,7 @@ import org.openrewrite.java.JavaParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; import static org.openrewrite.groovy.Assertions.groovy; import static org.openrewrite.java.Assertions.java; @@ -41,7 +42,8 @@ public void defaults(RecipeSpec spec) { "powermock-core-1.6", "testng-7.7" )) - .recipe(new PowerMockitoMockStaticToMockito()); + .recipe(new PowerMockitoMockStaticToMockito()) + .typeValidationOptions(TypeValidation.builder().cursorAcyclic(false).build()); } @DocumentExample @@ -78,7 +80,7 @@ void testStaticMethod() { import org.mockito.MockedStatic; public class MyTest { - + private MockedStatic mockedCalendar; @BeforeEach @@ -219,38 +221,38 @@ void tearDownMethodOfTestNGHasAnnotationWithArgument() { java( """ import java.util.Calendar; - + import org.testng.annotations.Test; import org.powermock.core.classloader.annotations.PrepareForTest; - + @PrepareForTest({Calendar.class}) public class MyTest { - + @Test void testSomething() { } - + } """, """ import java.util.Calendar; - + import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; - + public class MyTest { - + @BeforeMethod void setUpStaticMocks() { } - + @AfterMethod(alwaysRun = true) void tearDownStaticMocks() { } - + @Test void testSomething() { } - + } """ ) @@ -264,41 +266,41 @@ void tearDownMethodOfTestNGWithAnnotationRemainsUntouched() { java( """ import java.util.Calendar; - + import org.testng.annotations.AfterMethod; import org.testng.annotations.Test; import org.powermock.core.classloader.annotations.PrepareForTest; - + @PrepareForTest({Calendar.class}) public class MyTest { - + @AfterMethod(groups = "irrelevant") void tearDown() {} - + @Test void testSomething() { } - + } """, """ import java.util.Calendar; - + import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; - + public class MyTest { - + @AfterMethod(groups = "irrelevant") void tearDown() {} - + @BeforeMethod void setUpStaticMocks() { } - + @Test void testSomething() { } - + } """ ) @@ -312,22 +314,22 @@ void tearDownMethodOfTestNGHasAnnotationWithSameArgumentsAsTheTestThatCallsMockS java( """ import java.util.Calendar; - + import static org.mockito.Mockito.*; - + import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import org.powermock.core.classloader.annotations.PrepareForTest; - + @PrepareForTest({Calendar.class}) public class MyTest { - + private Calendar calendarMock; - + @Test(groups = "irrelevant") void testSomethingIrrelevantForCheckin() { } - + @Test(groups = "checkin") void testStaticMethod() { calendarMock = mock(Calendar.class); @@ -338,33 +340,33 @@ void testStaticMethod() { """, """ import java.util.Calendar; - + import static org.mockito.Mockito.*; - + import org.mockito.MockedStatic; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; - + public class MyTest { - + private MockedStatic mockedCalendar; - + private Calendar calendarMock; - + @BeforeMethod(groups = "checkin") void setUpStaticMocks() { mockedCalendar = mockStatic(Calendar.class); } - + @AfterMethod(groups = "checkin") void tearDownStaticMocks() { mockedCalendar.closeOnDemand(); } - + @Test(groups = "irrelevant") void testSomethingIrrelevantForCheckin() { } - + @Test(groups = "checkin") void testStaticMethod() { calendarMock = mock(Calendar.class); @@ -383,19 +385,19 @@ void argumentOfWhenOnStaticMethodWithParametersIsReplacedByLambda() { java( """ import static org.mockito.Mockito.*; - + import java.util.Calendar; import java.util.Locale; - + import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; - + public class MyTest { - + private MockedStatic mockedCalendar; - + private Calendar calendarMock = mock(Calendar.class); - + @Test void testStaticMethod() { when(Calendar.getInstance(Locale.ENGLISH)).thenReturn(calendarMock); @@ -404,19 +406,19 @@ void testStaticMethod() { """, """ import static org.mockito.Mockito.*; - + import java.util.Calendar; import java.util.Locale; - + import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; - + public class MyTest { - + private MockedStatic mockedCalendar; - + private Calendar calendarMock = mock(Calendar.class); - + @Test void testStaticMethod() { mockedCalendar.when(() -> Calendar.getInstance(Locale.ENGLISH)).thenReturn(calendarMock); @@ -434,16 +436,16 @@ void argumentOfWhenOnInstanceMethodsAreNotReplaced() { java( """ import static org.mockito.Mockito.*; - + import java.util.Calendar; import java.util.Locale; - + import org.junit.jupiter.api.Test; - + public class MyTest { - + private Calendar calendarMock = mock(Calendar.class); - + @Test void testStaticMethod() { when(calendarMock.toString()).thenReturn(null); @@ -461,19 +463,19 @@ void argumentOfVerifyOnParameterlessStaticMethodIsReplacedBySimpleLambda() { java( """ import static org.mockito.Mockito.*; - + import java.util.Currency; import java.util.Locale; - + import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; - + public class MyTest { - + private MockedStatic mockedCurrency; - + private Currency currencyMock = mock(Currency.class); - + @Test void testStaticMethod() { verify(Currency.getInstance(Locale.ENGLISH), never()); @@ -483,19 +485,19 @@ void testStaticMethod() { """, """ import static org.mockito.Mockito.*; - + import java.util.Currency; import java.util.Locale; - + import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; - + public class MyTest { - + private MockedStatic mockedCurrency; - + private Currency currencyMock = mock(Currency.class); - + @Test void testStaticMethod() { mockedCurrency.verify(() -> Currency.getInstance(Locale.ENGLISH), never()); @@ -519,13 +521,13 @@ public interface MyInterface { } """) , java( - """ + """ public abstract class MyAbstractClass { - + public boolean isItTrue() { return true; } - + public abstract boolean isItImplemented(); - + } """ )); @@ -537,14 +539,14 @@ void extensionOfPowerMockTestCaseGetsRemoved() { rewriteRun(java( """ package org.powermock.modules.testng; - + public class PowerMockTestCase {} """ ), java( """ import org.powermock.modules.testng.PowerMockTestCase; - + public class MyPowerMockTestCase extends PowerMockTestCase {} """, """ @@ -560,14 +562,14 @@ void extensionOfPowerMockConfigurationGetsRemoved() { java( """ package org.powermock.configuration; - + public class PowerMockConfiguration {} """ ), java( """ import org.powermock.configuration.PowerMockConfiguration; - + public class MyPowerMockConfiguration extends PowerMockConfiguration {} """, """ @@ -602,12 +604,111 @@ void testTryWithResource() { ); } + @Test + void shouldNotDuplicateVarsAndMethods() { + //language=java + rewriteRun( + java( + """ + package test; + + public class A { + public static class B { + public static String helloWorld() { + return "Hello World"; + } + } + } + """ + ), + java( + """ + import org.junit.Before; + import org.junit.Test; + import org.mockito.Mockito; + import org.powermock.core.classloader.annotations.PrepareForTest; + import test.A; + + @PrepareForTest({ A.B.class }) + public class MyTest { + + private static final String TEST_MESSAGE = "this is a test message"; + + @Before + void setUp() { + Mockito.mockStatic(A.B.class); + } + + @Test + public void testStaticMethod() { + } + } + """, + + """ + import org.junit.Before; + import org.junit.Test; + import org.junit.jupiter.api.AfterEach; + import org.junit.jupiter.api.BeforeEach; + import org.mockito.MockedStatic; + import org.mockito.Mockito; + import test.A; + + public class MyTest { + + private MockedStatic mockedA_B; + + private static final String TEST_MESSAGE = "this is a test message"; + + @Before + void setUp() { + } + + @BeforeEach + void setUpStaticMocks() { + mockedA_B = Mockito.mockStatic(A.B.class); + } + + @AfterEach + void tearDownStaticMocks() { + mockedA_B.closeOnDemand(); + } + + @Test + public void testStaticMethod() { + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/611") + void existingMockitoMockStaticShouldNotBeTouched() { + //language=java + rewriteRun( + java( + """ + import static org.mockito.Mockito.mockStatic; + + import org.mockito.MockedStatic; + + class TestClass { + MockedStatic mocked = mockStatic(String.class); + } + """ + ) + ); + } + @Test @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/358") void doesNotExplodeOnTopLevelMethodDeclaration() { rewriteRun( groovy( "def myFun() { }" - )); + ) + ); } } diff --git a/src/test/java/org/openrewrite/java/testing/mockito/RemoveMockitoSettingsTest.java b/src/test/java/org/openrewrite/java/testing/mockito/RemoveMockitoSettingsTest.java new file mode 100644 index 000000000..b0f3cbe98 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/mockito/RemoveMockitoSettingsTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2024 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.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 RemoveMockitoSettingsTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec + .parser(JavaParser.fromJavaVersion() + .classpathFromResources(new InMemoryExecutionContext(), + "mockito-core", "mockito-junit-jupiter", "junit-jupiter-api")) + .recipeFromResources("org.openrewrite.java.testing.mockito.MockitoBestPractices"); + } + + @Test + @DocumentExample + void removeMockitoSettings() { + rewriteRun( + //language=java + java( + """ + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; + @MockitoSettings(strictness = Strictness.WARN) + class A {} + """, + """ + class A {} + """ + ) + ); + } + + @Test + void removeMockitoSettingsFullyQualified() { + rewriteRun( + //language=java + java( + """ + import org.mockito.junit.jupiter.MockitoSettings; + @MockitoSettings(strictness = org.mockito.quality.Strictness.WARN) + class A {} + """, + """ + class A {} + """ + ) + ); + } + + @Test + void retainMisMatchedArgument() { + rewriteRun( + //language=java + java( + """ + import org.mockito.junit.jupiter.MockitoSettings; + import org.mockito.quality.Strictness; + @MockitoSettings(strictness = Strictness.LENIENT) + class A {} + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/mockito/ReplacePowerMockitoIntegrationTest.java b/src/test/java/org/openrewrite/java/testing/mockito/ReplacePowerMockitoIntegrationTest.java index 746f91e97..73162b8c0 100644 --- a/src/test/java/org/openrewrite/java/testing/mockito/ReplacePowerMockitoIntegrationTest.java +++ b/src/test/java/org/openrewrite/java/testing/mockito/ReplacePowerMockitoIntegrationTest.java @@ -39,8 +39,9 @@ public void defaults(RecipeSpec spec) { "powermock-api-mockito-1.6", "powermock-api-support-1.6", "testng-7.7")) - // TODO Resolve the missing types in the replacement templates rather than ignore the errors here .typeValidationOptions(TypeValidation.builder() + .cursorAcyclic(false) + // TODO Resolve the missing types in the replacement templates rather than ignore the errors here .identifiers(false) .methodInvocations(false) .build()) diff --git a/src/test/java/org/openrewrite/java/testing/mockito/SimplifyMockitoVerifyWhenGivenTest.java b/src/test/java/org/openrewrite/java/testing/mockito/SimplifyMockitoVerifyWhenGivenTest.java new file mode 100644 index 000000000..baa688cdf --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/mockito/SimplifyMockitoVerifyWhenGivenTest.java @@ -0,0 +1,303 @@ +/* + * Copyright 2024 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.DocumentExample; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Issue; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class SimplifyMockitoVerifyWhenGivenTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new SimplifyMockitoVerifyWhenGiven()) + .parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "mockito-core")); + } + + @DocumentExample + @Test + void shouldRemoveUnneccesaryEqFromVerify() { + rewriteRun( + //language=Java + java( + """ + import static org.mockito.Mockito.verify; + import static org.mockito.Mockito.mock; + import static org.mockito.ArgumentMatchers.eq; + + class Test { + void test() { + var mockString = mock(String.class); + verify(mockString).replace(eq("foo"), eq("bar")); + } + } + """, """ + import static org.mockito.Mockito.verify; + import static org.mockito.Mockito.mock; + + class Test { + void test() { + var mockString = mock(String.class); + verify(mockString).replace("foo", "bar"); + } + } + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite-testing-frameworks/issues/634") + void shouldRemoveUnneccesaryEqFromVerify_withMockitoStarImport() { + rewriteRun( + //language=Java + java( + """ + import static org.mockito.Mockito.eq; + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.verify; + + class Test { + void test() { + var mockString = mock(String.class); + verify(mockString).replace(eq("foo"), eq("bar")); + } + } + """, """ + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.verify; + + class Test { + void test() { + var mockString = mock(String.class); + verify(mockString).replace("foo", "bar"); + } + } + """ + ) + ); + } + + @Test + void shouldRemoveUnneccesaryEqFromWhen() { + rewriteRun( + //language=Java + java( + """ + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.when; + import static org.mockito.ArgumentMatchers.eq; + + class Test { + void test() { + var mockString = mock(String.class); + when(mockString.replace(eq("foo"), eq("bar"))).thenReturn("bar"); + } + } + """, """ + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.when; + + class Test { + void test() { + var mockString = mock(String.class); + when(mockString.replace("foo", "bar")).thenReturn("bar"); + } + } + """ + ) + ); + } + + @Test + void shouldNotRemoveEqWhenMatchersAreMixed() { + rewriteRun( + //language=Java + java( + """ + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.when; + import static org.mockito.ArgumentMatchers.eq; + import static org.mockito.ArgumentMatchers.anyString; + + class Test { + void test() { + var mockString = mock(String.class); + when(mockString.replace(eq("foo"), anyString())).thenReturn("bar"); + } + } + """ + ) + ); + } + + @Test + void shouldRemoveUnneccesaryEqFromStubber() { + rewriteRun( + //language=Java + java( + """ + import static org.mockito.Mockito.doThrow; + import static org.mockito.ArgumentMatchers.eq; + + class Test { + void test() { + doThrow(new RuntimeException()).when("foo").substring(eq(1)); + } + } + """, """ + import static org.mockito.Mockito.doThrow; + + class Test { + void test() { + doThrow(new RuntimeException()).when("foo").substring(1); + } + } + """ + ) + ); + } + + @Test + void shouldRemoveUnneccesaryEqFromBDDGiven() { + rewriteRun( + //language=Java + java( + """ + import static org.mockito.BDDMockito.given; + import static org.mockito.ArgumentMatchers.eq; + + class Test { + void test() { + given("foo".substring(eq(1))); + } + } + """, """ + import static org.mockito.BDDMockito.given; + + class Test { + void test() { + given("foo".substring(1)); + } + } + """ + ) + ); + } + + @Test + void shouldNotRemoveEqImportWhenStillNeeded() { + rewriteRun( + //language=Java + java( + """ + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.when; + import static org.mockito.ArgumentMatchers.eq; + import static org.mockito.ArgumentMatchers.anyString; + + class Test { + void testRemoveEq() { + var mockString = mock(String.class); + when(mockString.replace(eq("foo"), eq("bar"))).thenReturn("bar"); + } + + void testKeepEq() { + var mockString = mock(String.class); + when(mockString.replace(eq("foo"), anyString())).thenReturn("bar"); + } + } + """, """ + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.when; + import static org.mockito.ArgumentMatchers.eq; + import static org.mockito.ArgumentMatchers.anyString; + + class Test { + void testRemoveEq() { + var mockString = mock(String.class); + when(mockString.replace("foo", "bar")).thenReturn("bar"); + } + + void testKeepEq() { + var mockString = mock(String.class); + when(mockString.replace(eq("foo"), anyString())).thenReturn("bar"); + } + } + """ + ) + ); + } + + @Test + void shouldFixSonarExamples() { + rewriteRun( + //language=Java + java( + """ + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.when; + import static org.mockito.Mockito.verify; + import static org.mockito.Mockito.doThrow; + import static org.mockito.BDDMockito.given; + import static org.mockito.ArgumentMatchers.eq; + + class Test { + void test(Object v1, Object v2, Object v3, Object v4, Object v5, Foo foo) { + given(foo.bar(eq(v1), eq(v2), eq(v3))).willReturn(null); + when(foo.baz(eq(v4), eq(v5))).thenReturn("foo"); + doThrow(new RuntimeException()).when(foo).quux(eq(42)); + verify(foo).bar(eq(v1), eq(v2), eq(v3)); + } + } + + class Foo { + Object bar(Object v1, Object v2, Object v3) { return null; } + String baz(Object v4, Object v5) { return ""; } + void quux(int x) {} + } + """, """ + import static org.mockito.Mockito.mock; + import static org.mockito.Mockito.when; + import static org.mockito.Mockito.verify; + import static org.mockito.Mockito.doThrow; + import static org.mockito.BDDMockito.given; + + class Test { + void test(Object v1, Object v2, Object v3, Object v4, Object v5, Foo foo) { + given(foo.bar(v1, v2, v3)).willReturn(null); + when(foo.baz(v4, v5)).thenReturn("foo"); + doThrow(new RuntimeException()).when(foo).quux(42); + verify(foo).bar(v1, v2, v3); + } + } + + class Foo { + Object bar(Object v1, Object v2, Object v3) { return null; } + String baz(Object v4, Object v5) { return ""; } + void quux(int x) {} + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/mockito/VerifyZeroToNoMoreInteractionsTest.java b/src/test/java/org/openrewrite/java/testing/mockito/VerifyZeroToNoMoreInteractionsTest.java new file mode 100644 index 000000000..6b342323f --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/mockito/VerifyZeroToNoMoreInteractionsTest.java @@ -0,0 +1,214 @@ +/* + * Copyright 2024 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.intellij.lang.annotations.Language; +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; +import static org.openrewrite.maven.Assertions.pomXml; + +class VerifyZeroToNoMoreInteractionsTest implements RewriteTest { + + @Language("xml") + private static final String POM_XML_WITH_MOCKITO_2 = """ + + 4.0.0 + bla.bla + bla-bla + 1.0.0 + + + org.mockito + mockito-core + 2.17.0 + test + + + + """; + + @Override + public void defaults(RecipeSpec spec) { + spec + .parser(JavaParser.fromJavaVersion() + .classpathFromResources(new InMemoryExecutionContext(), "mockito-core-3", "mockito-junit-jupiter-3")) + .recipe(new VerifyZeroToNoMoreInteractions()); + } + + @Test + @DocumentExample + void shouldReplaceToNoMoreInteractions() { + //language=java + rewriteRun( + pomXml(POM_XML_WITH_MOCKITO_2), + java( + """ + import static org.mockito.Mockito.verifyZeroInteractions; + + class MyTest { + void test() { + verifyZeroInteractions(System.out); + } + } + """, + """ + import static org.mockito.Mockito.verifyNoMoreInteractions; + + class MyTest { + void test() { + verifyNoMoreInteractions(System.out); + } + } + """ + ) + ); + } + + @Test + void shouldNotReplaceToNoMoreInteractionsForImportOnly() { + //language=java + rewriteRun( + pomXml(POM_XML_WITH_MOCKITO_2), + java( + """ + import static org.mockito.Mockito.verifyZeroInteractions; + + class MyTest {} + """ + ) + ); + } + + @Test + void doesNotConvertAnyOtherMethods() { + rewriteRun( + pomXml(POM_XML_WITH_MOCKITO_2), + // language=java + java( + """ + import org.mockito.junit.jupiter.MockitoExtension; + import org.mockito.Mock; + import static org.mockito.Mockito.verifyZeroInteractions; + import static org.mockito.Mockito.verify; + + class MyTest { + @Mock + Object myObject; + + void test() { + verifyZeroInteractions(System.out); + verify(myObject); + } + } + """, + """ + import org.mockito.junit.jupiter.MockitoExtension; + import org.mockito.Mock; + import static org.mockito.Mockito.verifyNoMoreInteractions; + import static org.mockito.Mockito.verify; + + class MyTest { + @Mock + Object myObject; + + void test() { + verifyNoMoreInteractions(System.out); + verify(myObject); + } + } + """ + ) + ); + } + + @Test + void doesConvertNestedMethodInvocations() { + rewriteRun( + pomXml(POM_XML_WITH_MOCKITO_2), + // language=java + java( + """ + import java.util.function.Consumer; + + import static org.mockito.Mockito.verifyZeroInteractions; + + class MyTest { + void test() { + Runnable f = () -> verifyZeroInteractions(System.out); + f.run(); + } + } + """, + """ + import java.util.function.Consumer; + + import static org.mockito.Mockito.verifyNoMoreInteractions; + + class MyTest { + void test() { + Runnable f = () -> verifyNoMoreInteractions(System.out); + f.run(); + } + } + """ + ) + ); + } + + @Test + void shouldNotRunOnNewerMockito3OrHigher() { + rewriteRun( + //language=xml + pomXml( + """ + + 4.0.0 + bla.bla + bla-bla + 1.0.0 + + + org.mockito + mockito-core + 3.0.0 + test + + + + """), + //language=java + java( + """ + import org.mockito.junit.jupiter.MockitoExtension; + + import static org.mockito.Mockito.verifyZeroInteractions; + + class MyTest { + void test() { + verifyZeroInteractions(System.out); + } + } + """ + ) + ); + } +} diff --git a/src/test/resources/META-INF/rewrite/classpath/rider-spring-1.18.0.jar b/src/test/resources/META-INF/rewrite/classpath/rider-spring-1.18.0.jar new file mode 100644 index 000000000..6809b154b Binary files /dev/null and b/src/test/resources/META-INF/rewrite/classpath/rider-spring-1.18.0.jar differ diff --git a/src/test/resources/META-INF/rewrite/classpath/spring-test-6.1.12.jar b/src/test/resources/META-INF/rewrite/classpath/spring-test-6.1.12.jar new file mode 100644 index 000000000..df044b40f Binary files /dev/null and b/src/test/resources/META-INF/rewrite/classpath/spring-test-6.1.12.jar differ diff --git a/suppressions.xml b/suppressions.xml index fbf9371b9..cc839fdf3 100644 --- a/suppressions.xml +++ b/suppressions.xml @@ -1,3 +1,11 @@ + + + ^pkg:javascript/DOMPurify@.*$ + CVE-2024-45801 +