diff --git a/src/main/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxGetInternalStateToJavaReflection.java b/src/main/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxGetInternalStateToJavaReflection.java new file mode 100644 index 000000000..0ab3363da --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxGetInternalStateToJavaReflection.java @@ -0,0 +1,84 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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 lombok.Getter; +import org.jspecify.annotations.Nullable; +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + +import java.util.List; + +import static org.openrewrite.java.VariableNameUtils.GenerationStrategy.INCREMENT_NUMBER; +import static org.openrewrite.java.VariableNameUtils.generateVariableName; + +public class PowerMockWhiteboxGetInternalStateToJavaReflection extends Recipe { + + private static final MethodMatcher GET_INTERNAL_STATE = + new MethodMatcher("org.powermock.reflect.Whitebox getInternalState(java.lang.Object, java.lang.String)"); + + @Getter + final String displayName = "Replace PowerMock `Whitebox.getInternalState()` with Java reflection"; + + @Getter + final String description = "Replace `Whitebox.getInternalState(Object, String)` with `java.lang.reflect.Field` " + + "access, casting to the declared result type where needed. The field lookup uses `getDeclaredField` on " + + "the target object's class, which differs from PowerMock's class-hierarchy traversal for fields " + + "inherited from a superclass."; + + @Override + public TreeVisitor getVisitor() { + return new GetInternalStateVisitor().withPrecondition(); + } + + private static class GetInternalStateVisitor extends WhiteboxToReflectionVisitor { + + GetInternalStateVisitor() { + super("java.lang.reflect.Field", GET_INTERNAL_STATE); + } + + @Override + @Nullable String buildTemplate(J.MethodInvocation mi, ResultSink sink, Cursor scope, + JavaType.@Nullable Method resolvedMethod) { + String fieldName = extractStringLiteral(mi.getArguments().get(1)); + if (fieldName == null) { + return null; + } + String varName = generateVariableName(fieldName + "Field", scope, INCREMENT_NUMBER); + String prefix = fieldLookupPrefix(varName); + if (sink.varName != null) { + if (isNonObjectCast(sink.castType)) { + return prefix + sink.castType + " " + sink.varName + " = (" + sink.castType + ") " + varName + ".get(#{any(java.lang.Object)});"; + } + return prefix + "Object " + sink.varName + " = " + varName + ".get(#{any(java.lang.Object)});"; + } + return prefix + varName + ".get(#{any(java.lang.Object)});"; + } + + @Override + Object[] buildArgs(J.MethodInvocation mi, JavaType.@Nullable Method resolvedMethod) { + List args = mi.getArguments(); + // target, fieldName, target + return new Object[]{args.get(0), args.get(1), args.get(0)}; + } + } +} diff --git a/src/main/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxInvokeMethodToJavaReflection.java b/src/main/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxInvokeMethodToJavaReflection.java new file mode 100644 index 000000000..4e7b679a5 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxInvokeMethodToJavaReflection.java @@ -0,0 +1,185 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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 lombok.Getter; +import org.jspecify.annotations.Nullable; +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.MethodMatcher; +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; + +import static org.openrewrite.java.VariableNameUtils.GenerationStrategy.INCREMENT_NUMBER; +import static org.openrewrite.java.VariableNameUtils.generateVariableName; + +public class PowerMockWhiteboxInvokeMethodToJavaReflection extends Recipe { + + private static final MethodMatcher INVOKE_METHOD = + new MethodMatcher("org.powermock.reflect.Whitebox invokeMethod(java.lang.Object, java.lang.String, ..)"); + + @Getter + final String displayName = "Replace PowerMock `Whitebox.invokeMethod()` with Java reflection"; + + @Getter + final String description = "Replace `Whitebox.invokeMethod(Object, String, ..)` with `java.lang.reflect.Method` " + + "lookup and `invoke()`. Parameter types are taken from the unambiguously resolved target method, " + + "falling back to each argument's compile-time class."; + + @Override + public TreeVisitor getVisitor() { + return new InvokeMethodVisitor().withPrecondition(); + } + + private static class InvokeMethodVisitor extends WhiteboxToReflectionVisitor { + + InvokeMethodVisitor() { + super("java.lang.reflect.Method", INVOKE_METHOD); + } + + @Override + JavaType.@Nullable Method resolve(J.MethodInvocation mi) { + return resolveTargetMethod(mi.getArguments()); + } + + @Override + @Nullable String buildTemplate(J.MethodInvocation mi, ResultSink sink, Cursor scope, + JavaType.@Nullable Method resolvedMethod) { + List args = mi.getArguments(); + String methodName = extractStringLiteral(args.get(1)); + if (methodName == null) { + return null; + } + String varName = generateVariableName(methodName + "Method", scope, INCREMENT_NUMBER); + + // getDeclaredMethod line + StringBuilder sb = new StringBuilder(); + sb.append("Method ").append(varName).append(" = #{any(java.lang.Object)}.getClass().getDeclaredMethod(#{any(java.lang.String)}"); + for (int i = 2; i < args.size(); i++) { + String classLiteral = getParamClassLiteral(args, i, resolvedMethod); + if (classLiteral != null) { + sb.append(", ").append(classLiteral); + } else { + sb.append(", #{any(java.lang.Object)}.getClass()"); + } + } + sb.append(");\n"); + + // setAccessible line + sb.append(varName).append(".setAccessible(true);\n"); + + // invoke line + if (sink.varName != null) { + if (isNonObjectCast(sink.castType)) { + sb.append(sink.castType).append(" ").append(sink.varName).append(" = (").append(sink.castType).append(") "); + } else { + sb.append("Object ").append(sink.varName).append(" = "); + } + } + sb.append(varName).append(".invoke(#{any(java.lang.Object)}"); + for (int i = 2; i < args.size(); i++) { + sb.append(", #{any(java.lang.Object)}"); + } + sb.append(");"); + + return sb.toString(); + } + + @Override + Object[] buildArgs(J.MethodInvocation mi, JavaType.@Nullable Method resolvedMethod) { + List args = mi.getArguments(); + List result = new ArrayList<>(); + result.add(args.get(0)); // target for getDeclaredMethod + result.add(args.get(1)); // methodName + for (int i = 2; i < args.size(); i++) { + if (getParamClassLiteral(args, i, resolvedMethod) == null) { + result.add(args.get(i)); // arg.getClass() fallback for getDeclaredMethod + } + } + result.add(args.get(0)); // target for invoke + for (int i = 2; i < args.size(); i++) { + result.add(args.get(i)); // arg for invoke + } + return result.toArray(); + } + + /** + * Get the class literal for a parameter at the given argument index. Prefers the resolved + * method's declared parameter type, falls back to the argument's compile-time type, and + * returns null if neither is available. + */ + private @Nullable String getParamClassLiteral(List args, int argIndex, + JavaType.@Nullable Method resolvedMethod) { + if (resolvedMethod != null) { + String literal = classLiteralFromType(resolvedMethod.getParameterTypes().get(argIndex - 2)); + if (literal != null) { + return literal; + } + } + return classLiteralFromType(args.get(argIndex).getType()); + } + + /** + * Resolve the target method from the first argument's type and the method name, walking the + * supertype chain. Returns null if the method cannot be unambiguously resolved (not found, + * overloaded, or missing type information). + */ + private JavaType.@Nullable Method resolveTargetMethod(List args) { + if (args.size() <= 2) { + return null; + } + String methodName = extractStringLiteral(args.get(1)); + if (methodName == null) { + return null; + } + JavaType targetType = args.get(0).getType(); + if (!(targetType instanceof JavaType.FullyQualified)) { + return null; + } + int expectedParamCount = args.size() - 2; + JavaType.Method match = null; + for (JavaType.FullyQualified current = (JavaType.FullyQualified) targetType; + current != null; current = current.getSupertype()) { + for (JavaType.Method method : current.getMethods()) { + if (method.getName().equals(methodName) && + method.getParameterTypes().size() == expectedParamCount) { + if (match != null) { + return null; // ambiguous overload + } + match = method; + } + } + } + return match; + } + + private @Nullable String classLiteralFromType(@Nullable JavaType type) { + if (type instanceof JavaType.Primitive) { + return ((JavaType.Primitive) type).getKeyword() + ".class"; + } + if (type instanceof JavaType.FullyQualified) { + return ((JavaType.FullyQualified) type).getClassName() + ".class"; + } + return null; + } + } +} diff --git a/src/main/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxSetInternalStateToJavaReflection.java b/src/main/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxSetInternalStateToJavaReflection.java new file mode 100644 index 000000000..cc3d8035c --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxSetInternalStateToJavaReflection.java @@ -0,0 +1,87 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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 lombok.Getter; +import org.jspecify.annotations.Nullable; +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; + +import java.util.List; + +import static org.openrewrite.java.VariableNameUtils.GenerationStrategy.INCREMENT_NUMBER; +import static org.openrewrite.java.VariableNameUtils.generateVariableName; + +public class PowerMockWhiteboxSetInternalStateToJavaReflection extends Recipe { + + private static final MethodMatcher SET_INTERNAL_STATE = + new MethodMatcher("org.powermock.reflect.Whitebox setInternalState(java.lang.Object, java.lang.String, java.lang.Object)"); + private static final MethodMatcher SET_INTERNAL_STATE_WHERE = + new MethodMatcher("org.powermock.reflect.Whitebox setInternalState(java.lang.Object, java.lang.String, java.lang.Object, java.lang.Class)"); + + @Getter + final String displayName = "Replace PowerMock `Whitebox.setInternalState()` with Java reflection"; + + @Getter + final String description = "Replace `Whitebox.setInternalState(Object, String, Object)` and " + + "`Whitebox.setInternalState(Object, String, Object, Class)` with `java.lang.reflect.Field` access. " + + "The 3-arg overload looks up the field on the target's class; the 4-arg where-overload uses the " + + "supplied Class to resolve fields declared on a superclass."; + + @Override + public TreeVisitor getVisitor() { + return new SetInternalStateVisitor().withPrecondition(); + } + + private static class SetInternalStateVisitor extends WhiteboxToReflectionVisitor { + + SetInternalStateVisitor() { + super("java.lang.reflect.Field", SET_INTERNAL_STATE, SET_INTERNAL_STATE_WHERE); + } + + @Override + @Nullable String buildTemplate(J.MethodInvocation mi, ResultSink sink, Cursor scope, + JavaType.@Nullable Method resolvedMethod) { + String fieldName = extractStringLiteral(mi.getArguments().get(1)); + if (fieldName == null) { + return null; + } + String varName = generateVariableName(fieldName + "Field", scope, INCREMENT_NUMBER); + String prefix = mi.getArguments().size() == 4 + ? fieldLookupPrefixWhere(varName) + : fieldLookupPrefix(varName); + return prefix + + varName + ".set(#{any(java.lang.Object)}, #{any(java.lang.Object)});"; + } + + @Override + Object[] buildArgs(J.MethodInvocation mi, JavaType.@Nullable Method resolvedMethod) { + List args = mi.getArguments(); + if (args.size() == 4) { + // whereClass, fieldName, target, value + return new Object[]{args.get(3), args.get(1), args.get(0), args.get(2)}; + } + // target, fieldName, target, value + return new Object[]{args.get(0), args.get(1), args.get(0), args.get(2)}; + } + } +} diff --git a/src/main/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxToJavaReflection.java b/src/main/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxToJavaReflection.java deleted file mode 100644 index a267d144b..000000000 --- a/src/main/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxToJavaReflection.java +++ /dev/null @@ -1,404 +0,0 @@ -/* - * Copyright 2026 the original author or authors. - *

- * Licensed under the Moderne Source Available License (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * https://docs.moderne.io/licensing/moderne-source-available-license - *

- * 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 lombok.Getter; -import org.jspecify.annotations.Nullable; -import org.openrewrite.*; -import org.openrewrite.internal.ListUtils; -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.UsesType; -import org.openrewrite.java.tree.*; -import org.openrewrite.marker.Markers; - -import java.util.ArrayList; -import java.util.List; - -import static java.util.Collections.emptyList; -import static org.openrewrite.Tree.randomId; -import static org.openrewrite.java.VariableNameUtils.GenerationStrategy.INCREMENT_NUMBER; -import static org.openrewrite.java.VariableNameUtils.generateVariableName; - -public class PowerMockWhiteboxToJavaReflection extends Recipe { - - private static final String WHITEBOX_FQN = "org.powermock.reflect.Whitebox"; - private static final MethodMatcher SET_INTERNAL_STATE = - new MethodMatcher("org.powermock.reflect.Whitebox setInternalState(java.lang.Object, java.lang.String, java.lang.Object)"); - private static final MethodMatcher SET_INTERNAL_STATE_WHERE = - new MethodMatcher("org.powermock.reflect.Whitebox setInternalState(java.lang.Object, java.lang.String, java.lang.Object, java.lang.Class)"); - private static final MethodMatcher GET_INTERNAL_STATE = - new MethodMatcher("org.powermock.reflect.Whitebox getInternalState(java.lang.Object, java.lang.String)"); - private static final MethodMatcher INVOKE_METHOD = - new MethodMatcher("org.powermock.reflect.Whitebox invokeMethod(java.lang.Object, java.lang.String, ..)"); - - @Getter - final String displayName = "Replace PowerMock `Whitebox` with Java reflection"; - - @Getter - final String description = "Replace `org.powermock.reflect.Whitebox` calls " + - "(`setInternalState`, `getInternalState`, `invokeMethod`) with plain Java reflection using " + - "`java.lang.reflect.Field` and `java.lang.reflect.Method`."; - - @Override - public TreeVisitor getVisitor() { - return Preconditions.check( - new UsesType<>(WHITEBOX_FQN, false), - new JavaIsoVisitor() { - - private static final String WHITEBOX_REPLACED = "whiteboxReplaced"; - private static final String NEEDS_FIELD_IMPORT = "needsFieldImport"; - private static final String NEEDS_METHOD_IMPORT = "needsMethodImport"; - - @Override - public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { - J.MethodDeclaration md = super.visitMethodDeclaration(method, ctx); - if (getCursor().getMessage(WHITEBOX_REPLACED, false)) { - md = addThrowsExceptionIfAbsent(md); - maybeRemoveImport(WHITEBOX_FQN); - if (getCursor().getMessage(NEEDS_FIELD_IMPORT, false)) { - maybeAddImport("java.lang.reflect.Field", false); - } - if (getCursor().getMessage(NEEDS_METHOD_IMPORT, false)) { - maybeAddImport("java.lang.reflect.Method", false); - } - return maybeAutoFormat(method, md, ctx); - } - return md; - } - - @Override - public J.Block visitBlock(J.Block block, ExecutionContext ctx) { - J.Block b = super.visitBlock(block, ctx); - - List statements = b.getStatements(); - // Process in reverse so that coordinate positions remain valid after each replacement - for (int i = statements.size() - 1; i >= 0; i--) { - Statement stmt = statements.get(i); - J.MethodInvocation mi = extractWhiteboxInvocation(stmt); - if (mi == null) { - continue; - } - Cursor blockCursor = new Cursor(getCursor().getParentOrThrow(), b); - JavaType.Method resolvedMethod = INVOKE_METHOD.matches(mi) ? - resolveTargetMethod(mi.getArguments()) : null; - String template = buildReplacementTemplate(stmt, mi, blockCursor, resolvedMethod); - if (template != null) { - Object[] templateArgs = buildTemplateArgs(mi, resolvedMethod); - - List templateImports = new ArrayList<>(); - templateImports.add("java.lang.reflect.Field"); - templateImports.add("java.lang.reflect.Method"); - if (resolvedMethod != null) { - for (JavaType paramType : resolvedMethod.getParameterTypes()) { - if (paramType instanceof JavaType.FullyQualified) { - JavaType.FullyQualified fq = (JavaType.FullyQualified) paramType; - if (!"java.lang".equals(fq.getPackageName())) { - templateImports.add(fq.getFullyQualifiedName()); - maybeAddImport(fq.getFullyQualifiedName()); - } - } - } - } - - b = JavaTemplate.builder(template) - .contextSensitive() - .javaParser(JavaParser.fromJavaVersion()) - .imports(templateImports.toArray(new String[0])) - .build() - .apply( - new Cursor(getCursor().getParentOrThrow(), b), - stmt.getCoordinates().replace(), - templateArgs - ); - getCursor().putMessageOnFirstEnclosing(J.MethodDeclaration.class, WHITEBOX_REPLACED, true); - if (SET_INTERNAL_STATE.matches(mi) || SET_INTERNAL_STATE_WHERE.matches(mi) || GET_INTERNAL_STATE.matches(mi)) { - getCursor().putMessageOnFirstEnclosing(J.MethodDeclaration.class, NEEDS_FIELD_IMPORT, true); - } - if (INVOKE_METHOD.matches(mi)) { - getCursor().putMessageOnFirstEnclosing(J.MethodDeclaration.class, NEEDS_METHOD_IMPORT, true); - } - // Re-read statements list since the block has been rebuilt - statements = b.getStatements(); - } - } - - return b; - } - - private @Nullable String buildReplacementTemplate(Statement statement, J.MethodInvocation mi, - Cursor scope, JavaType.@Nullable Method resolvedMethod) { - List args = mi.getArguments(); - - if (SET_INTERNAL_STATE.matches(mi) && args.size() == 3) { - return buildSetInternalStateTemplate(args, scope, false); - } - if (SET_INTERNAL_STATE_WHERE.matches(mi) && args.size() == 4) { - return buildSetInternalStateTemplate(args, scope, true); - } - if (GET_INTERNAL_STATE.matches(mi) && args.size() == 2) { - return buildGetInternalStateTemplate(args, statement, scope); - } - if (INVOKE_METHOD.matches(mi) && args.size() >= 2) { - return buildInvokeMethodTemplate(args, statement, scope, resolvedMethod); - } - return null; - } - - private Object[] buildTemplateArgs(J.MethodInvocation mi, JavaType.@Nullable Method resolvedMethod) { - List args = mi.getArguments(); - - if (SET_INTERNAL_STATE.matches(mi) && args.size() == 3) { - // target, fieldName, target, value - return new Object[]{args.get(0), args.get(1), args.get(0), args.get(2)}; - } - if (SET_INTERNAL_STATE_WHERE.matches(mi) && args.size() == 4) { - // whereClass, fieldName, target, value - return new Object[]{args.get(3), args.get(1), args.get(0), args.get(2)}; - } - if (GET_INTERNAL_STATE.matches(mi) && args.size() == 2) { - // target, fieldName, target - return new Object[]{args.get(0), args.get(1), args.get(0)}; - } - if (INVOKE_METHOD.matches(mi) && args.size() >= 2) { - return buildInvokeMethodArgs(args, resolvedMethod); - } - return new Object[0]; - } - - private @Nullable String buildSetInternalStateTemplate(List args, Cursor scope, boolean where) { - String fieldName = extractStringLiteral(args.get(1)); - if (fieldName == null) { - return null; - } - String varName = generateVariableName(fieldName + "Field", scope, INCREMENT_NUMBER); - String lookup = where ? - "Field " + varName + " = #{any(java.lang.Class)}.getDeclaredField(#{any(java.lang.String)});\n" : - "Field " + varName + " = #{any(java.lang.Object)}.getClass().getDeclaredField(#{any(java.lang.String)});\n"; - return lookup + - varName + ".setAccessible(true);\n" + - varName + ".set(#{any(java.lang.Object)}, #{any(java.lang.Object)});"; - } - - private @Nullable String buildGetInternalStateTemplate(List args, Statement statement, Cursor scope) { - String fieldName = extractStringLiteral(args.get(1)); - if (fieldName == null) { - return null; - } - String varName = generateVariableName(fieldName + "Field", scope, INCREMENT_NUMBER); - String prefix = "Field " + varName + " = #{any(java.lang.Object)}.getClass().getDeclaredField(#{any(java.lang.String)});\n" + - varName + ".setAccessible(true);\n"; - - if (statement instanceof J.VariableDeclarations) { - J.VariableDeclarations varDecls = (J.VariableDeclarations) statement; - String assignToVar = varDecls.getVariables().get(0).getSimpleName(); - String castType = getCastType(varDecls.getType()); - if (castType != null && !"Object".equals(castType) && !"java.lang.Object".equals(castType)) { - return prefix + castType + " " + assignToVar + " = (" + castType + ") " + varName + ".get(#{any(java.lang.Object)});"; - } - return prefix + "Object " + assignToVar + " = " + varName + ".get(#{any(java.lang.Object)});"; - } - return prefix + varName + ".get(#{any(java.lang.Object)});"; - } - - private @Nullable String buildInvokeMethodTemplate(List args, Statement statement, - Cursor scope, JavaType.@Nullable Method resolvedMethod) { - String methodName = extractStringLiteral(args.get(1)); - if (methodName == null) { - return null; - } - String varName = generateVariableName(methodName + "Method", scope, INCREMENT_NUMBER); - - // getDeclaredMethod line - StringBuilder sb = new StringBuilder(); - sb.append("Method ").append(varName).append(" = #{any(java.lang.Object)}.getClass().getDeclaredMethod(#{any(java.lang.String)}"); - for (int i = 2; i < args.size(); i++) { - String classLiteral = getParamClassLiteral(args, i, resolvedMethod); - if (classLiteral != null) { - sb.append(", ").append(classLiteral); - } else { - sb.append(", #{any(java.lang.Object)}.getClass()"); - } - } - sb.append(");\n"); - - // setAccessible line - sb.append(varName).append(".setAccessible(true);\n"); - - // invoke line - if (statement instanceof J.VariableDeclarations) { - J.VariableDeclarations varDecls = (J.VariableDeclarations) statement; - String assignToVar = varDecls.getVariables().get(0).getSimpleName(); - String castType = getCastType(varDecls.getType()); - if (castType != null && !"Object".equals(castType) && !"java.lang.Object".equals(castType)) { - sb.append(castType).append(" ").append(assignToVar).append(" = (").append(castType).append(") "); - } else { - sb.append("Object ").append(assignToVar).append(" = "); - } - } - sb.append(varName).append(".invoke(#{any(java.lang.Object)}"); - for (int i = 2; i < args.size(); i++) { - sb.append(", #{any(java.lang.Object)}"); - } - sb.append(");"); - - return sb.toString(); - } - - private Object[] buildInvokeMethodArgs(List args, JavaType.@Nullable Method resolvedMethod) { - int extraArgs = args.size() - 2; - int unresolvedCount = 0; - for (int i = 2; i < args.size(); i++) { - if (getParamClassLiteral(args, i, resolvedMethod) == null) { - unresolvedCount++; - } - } - Object[] result = new Object[2 + unresolvedCount + 1 + extraArgs]; - int idx = 0; - result[idx++] = args.get(0); // target for getDeclaredMethod - result[idx++] = args.get(1); // methodName - for (int i = 2; i < args.size(); i++) { - if (getParamClassLiteral(args, i, resolvedMethod) == null) { - result[idx++] = args.get(i); // arg.getClass() fallback for getDeclaredMethod - } - } - result[idx++] = args.get(0); // target for invoke - for (int i = 2; i < args.size(); i++) { - result[idx++] = args.get(i); // arg for invoke - } - return result; - } - - /** - * Get the class literal for a parameter at the given argument index. - * Prefers the resolved method's declared parameter type, falls back to the argument's - * compile-time type, and returns null if neither is available. - */ - private @Nullable String getParamClassLiteral(List args, int argIndex, - JavaType.@Nullable Method resolvedMethod) { - if (resolvedMethod != null) { - String literal = classLiteralFromType(resolvedMethod.getParameterTypes().get(argIndex - 2)); - if (literal != null) { - return literal; - } - } - return getClassLiteral(args.get(argIndex)); - } - - /** - * Resolve the target method from the first argument's type and the method name. - * Returns null if the method cannot be unambiguously resolved (not found, overloaded, - * or missing type information). - */ - private JavaType.@Nullable Method resolveTargetMethod(List args) { - if (args.size() <= 2) { - return null; - } - String methodName = extractStringLiteral(args.get(1)); - if (methodName == null) { - return null; - } - JavaType targetType = args.get(0).getType(); - if (!(targetType instanceof JavaType.FullyQualified)) { - return null; - } - int expectedParamCount = args.size() - 2; - JavaType.Method match = null; - for (JavaType.FullyQualified current = (JavaType.FullyQualified) targetType; - current != null; current = current.getSupertype()) { - for (JavaType.Method method : current.getMethods()) { - if (method.getName().equals(methodName) && - method.getParameterTypes().size() == expectedParamCount) { - if (match != null) { - return null; // ambiguous overload - } - match = method; - } - } - } - return match; - } - - private J.@Nullable MethodInvocation extractWhiteboxInvocation(Statement statement) { - if (statement instanceof J.MethodInvocation) { - J.MethodInvocation mi = (J.MethodInvocation) statement; - if (SET_INTERNAL_STATE.matches(mi) || SET_INTERNAL_STATE_WHERE.matches(mi) || - GET_INTERNAL_STATE.matches(mi) || INVOKE_METHOD.matches(mi)) { - return mi; - } - } - if (statement instanceof J.VariableDeclarations) { - J.VariableDeclarations varDecls = (J.VariableDeclarations) statement; - if (varDecls.getVariables().size() == 1) { - Expression init = varDecls.getVariables().get(0).getInitializer(); - if (init instanceof J.MethodInvocation) { - J.MethodInvocation mi = (J.MethodInvocation) init; - if (GET_INTERNAL_STATE.matches(mi) || INVOKE_METHOD.matches(mi)) { - return mi; - } - } - } - } - return null; - } - - private @Nullable String extractStringLiteral(Expression expr) { - if (expr instanceof J.Literal && ((J.Literal) expr).getValue() instanceof String) { - return (String) ((J.Literal) expr).getValue(); - } - return null; - } - - private @Nullable String getClassLiteral(Expression expr) { - return classLiteralFromType(expr.getType()); - } - - private @Nullable String classLiteralFromType(@Nullable JavaType type) { - if (type instanceof JavaType.Primitive) { - return ((JavaType.Primitive) type).getKeyword() + ".class"; - } - if (type instanceof JavaType.FullyQualified) { - return ((JavaType.FullyQualified) type).getClassName() + ".class"; - } - return null; - } - - private @Nullable String getCastType(@Nullable JavaType type) { - if (type instanceof JavaType.FullyQualified) { - return ((JavaType.FullyQualified) type).getClassName(); - } - if (type instanceof JavaType.Primitive) { - return ((JavaType.Primitive) type).getKeyword(); - } - return null; - } - - private J.MethodDeclaration addThrowsExceptionIfAbsent(J.MethodDeclaration md) { - if (md.getThrows() != null && md.getThrows().stream() - .anyMatch(j -> TypeUtils.isOfClassType(j.getType(), "java.lang.Exception") || - TypeUtils.isOfClassType(j.getType(), "java.lang.Throwable"))) { - return md; - } - JavaType.Class exceptionType = JavaType.ShallowClass.build("java.lang.Exception"); - return md.withThrows(ListUtils.concat(md.getThrows(), - new J.Identifier(randomId(), Space.SINGLE_SPACE, Markers.EMPTY, emptyList(), - exceptionType.getClassName(), exceptionType, null))); - } - }); - } -} diff --git a/src/main/java/org/openrewrite/java/testing/mockito/WhiteboxToReflectionVisitor.java b/src/main/java/org/openrewrite/java/testing/mockito/WhiteboxToReflectionVisitor.java new file mode 100644 index 000000000..1f121f85d --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/mockito/WhiteboxToReflectionVisitor.java @@ -0,0 +1,278 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.Preconditions; +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.MethodMatcher; +import org.openrewrite.java.search.UsesMethod; +import org.openrewrite.java.tree.*; +import org.openrewrite.marker.Markers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static java.util.Collections.emptyList; +import static org.openrewrite.Tree.randomId; + +/** + * Shared machinery for replacing a single {@code org.powermock.reflect.Whitebox} API family with + * plain Java reflection: extracts migratable Whitebox calls from block statements, applies the + * family's replacement template, and maintains the surrounding method declaration + * ({@code throws Exception}, imports, formatting). Subclasses configure the family's matchers and + * required {@code java.lang.reflect} import via the constructor and supply the template and its + * arguments. + */ +abstract class WhiteboxToReflectionVisitor extends JavaIsoVisitor { + + static final String WHITEBOX_FQN = "org.powermock.reflect.Whitebox"; + + private static final String WHITEBOX_REPLACED = "whiteboxReplaced"; + + private final String reflectiveImport; + private final List matchers; + + WhiteboxToReflectionVisitor(String reflectiveImport, MethodMatcher... matchers) { + this.reflectiveImport = reflectiveImport; + this.matchers = Arrays.asList(matchers); + } + + /** + * Where a result-producing reflection call stores its result. {@code varName} is the declared + * variable receiving the value, or null when the result is discarded (the call was a bare + * statement); {@code castType} is that variable's declared type. + */ + static final class ResultSink { + final @Nullable String varName; + final @Nullable String castType; + + private ResultSink(@Nullable String varName, @Nullable String castType) { + this.varName = varName; + this.castType = castType; + } + } + + /** + * The replacement template for the matched call, or null when the call cannot be mechanically + * migrated and must be left unchanged. + */ + abstract @Nullable String buildTemplate(J.MethodInvocation mi, ResultSink sink, Cursor scope, + JavaType.@Nullable Method resolvedMethod); + + /** + * The template arguments matching {@link #buildTemplate}'s placeholders. + */ + abstract Object[] buildArgs(J.MethodInvocation mi, JavaType.@Nullable Method resolvedMethod); + + /** + * The target method the call reflects on, when it can be unambiguously resolved; used to derive + * declared parameter types for class literals. + */ + JavaType.@Nullable Method resolve(J.MethodInvocation mi) { + return null; + } + + /** + * This visitor gated so that only source files calling one of the configured Whitebox methods + * are traversed. + */ + TreeVisitor withPrecondition() { + TreeVisitor precondition = new UsesMethod<>(matchers.get(0)); + for (int i = 1; i < matchers.size(); i++) { + precondition = Preconditions.or(precondition, new UsesMethod<>(matchers.get(i))); + } + return Preconditions.check(precondition, this); + } + + private boolean matches(J.MethodInvocation mi) { + for (MethodMatcher matcher : matchers) { + if (matcher.matches(mi)) { + return true; + } + } + return false; + } + + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + J.MethodDeclaration md = super.visitMethodDeclaration(method, ctx); + if (getCursor().getMessage(WHITEBOX_REPLACED, false)) { + md = addThrowsExceptionIfAbsent(md); + maybeRemoveImport(WHITEBOX_FQN); + maybeAddImport(reflectiveImport, false); + return maybeAutoFormat(method, md, ctx); + } + return md; + } + + @Override + public J.Block visitBlock(J.Block block, ExecutionContext ctx) { + J.Block b = super.visitBlock(block, ctx); + + // Replace Whitebox calls that are a statement or single-variable declaration initializer, in + // reverse so coordinates stay valid: each JavaTemplate.apply rebuilds the block, letting + // generateVariableName see prior locals (ListUtils.flatMap would need manual name dedup). + List statements = b.getStatements(); + for (int i = statements.size() - 1; i >= 0; i--) { + Statement stmt = statements.get(i); + J.MethodInvocation mi = extractWhiteboxInvocation(stmt); + if (mi == null) { + continue; + } + Cursor blockCursor = new Cursor(getCursor().getParentOrThrow(), b); + JavaType.Method resolvedMethod = resolve(mi); + String template = buildTemplate(mi, sinkFromStatement(stmt), blockCursor, resolvedMethod); + if (template != null) { + b = JavaTemplate.builder(template) + .contextSensitive() + .javaParser(JavaParser.fromJavaVersion()) + .imports(templateImports(resolvedMethod).toArray(new String[0])) + .build() + .apply( + new Cursor(getCursor().getParentOrThrow(), b), + stmt.getCoordinates().replace(), + buildArgs(mi, resolvedMethod) + ); + recordReplacement(resolvedMethod); + // Re-read statements list since the block has been rebuilt + statements = b.getStatements(); + } + } + + return b; + } + + private void recordReplacement(JavaType.@Nullable Method resolvedMethod) { + getCursor().putMessageOnFirstEnclosing(J.MethodDeclaration.class, WHITEBOX_REPLACED, true); + for (String paramImport : resolvedParamImports(resolvedMethod)) { + maybeAddImport(paramImport); + } + } + + private List templateImports(JavaType.@Nullable Method resolvedMethod) { + List imports = new ArrayList<>(); + imports.add(reflectiveImport); + imports.addAll(resolvedParamImports(resolvedMethod)); + return imports; + } + + // Non-java.lang fully-qualified parameter types of the resolved method that the generated class + // literals (e.g. `List.class`) need imported. + private List resolvedParamImports(JavaType.@Nullable Method resolvedMethod) { + if (resolvedMethod == null) { + return emptyList(); + } + List imports = new ArrayList<>(); + for (JavaType paramType : resolvedMethod.getParameterTypes()) { + JavaType.FullyQualified fq = TypeUtils.asFullyQualified(paramType); + if (fq != null && !"java.lang".equals(fq.getPackageName())) { + imports.add(fq.getFullyQualifiedName()); + } + } + return imports; + } + + private ResultSink sinkFromStatement(Statement statement) { + if (statement instanceof J.VariableDeclarations) { + J.VariableDeclarations vd = (J.VariableDeclarations) statement; + return new ResultSink(vd.getVariables().get(0).getSimpleName(), getCastType(vd.getType())); + } + return new ResultSink(null, null); + } + + private J.@Nullable MethodInvocation extractWhiteboxInvocation(Statement statement) { + if (statement instanceof J.MethodInvocation) { + J.MethodInvocation mi = (J.MethodInvocation) statement; + if (matches(mi)) { + return mi; + } + } + if (statement instanceof J.VariableDeclarations) { + J.VariableDeclarations varDecls = (J.VariableDeclarations) statement; + if (varDecls.getVariables().size() == 1) { + Expression init = varDecls.getVariables().get(0).getInitializer(); + if (init instanceof J.MethodInvocation) { + J.MethodInvocation mi = (J.MethodInvocation) init; + // A void call (setInternalState) cannot initialize a variable declaration. + if (matches(mi) && !returnsVoid(mi)) { + return mi; + } + } + } + } + return null; + } + + private boolean returnsVoid(J.MethodInvocation mi) { + return mi.getMethodType() != null && JavaType.Primitive.Void == mi.getMethodType().getReturnType(); + } + + // `Field = .getClass().getDeclaredField(); .setAccessible(true);` — + // shared by the get/set field variants. + String fieldLookupPrefix(String varName) { + return "Field " + varName + " = #{any(java.lang.Object)}.getClass().getDeclaredField(#{any(java.lang.String)});\n" + + varName + ".setAccessible(true);\n"; + } + + // `Field = .getDeclaredField(); .setAccessible(true);` — + // used by the 4-arg setInternalState(target, field, value, Class) where-overload. + String fieldLookupPrefixWhere(String varName) { + return "Field " + varName + " = #{any(java.lang.Class)}.getDeclaredField(#{any(java.lang.String)});\n" + + varName + ".setAccessible(true);\n"; + } + + // True when castType denotes a meaningful type to cast to (i.e. not null and not Object). + boolean isNonObjectCast(@Nullable String castType) { + return castType != null && !"Object".equals(castType) && !"java.lang.Object".equals(castType); + } + + @Nullable String extractStringLiteral(Expression expr) { + if (expr instanceof J.Literal && ((J.Literal) expr).getValue() instanceof String) { + return (String) ((J.Literal) expr).getValue(); + } + return null; + } + + @Nullable String getCastType(@Nullable JavaType type) { + if (type instanceof JavaType.FullyQualified) { + return ((JavaType.FullyQualified) type).getClassName(); + } + if (type instanceof JavaType.Primitive) { + return ((JavaType.Primitive) type).getKeyword(); + } + return null; + } + + private J.MethodDeclaration addThrowsExceptionIfAbsent(J.MethodDeclaration md) { + if (md.getThrows() != null && md.getThrows().stream() + .anyMatch(j -> TypeUtils.isOfClassType(j.getType(), "java.lang.Exception") || + TypeUtils.isOfClassType(j.getType(), "java.lang.Throwable"))) { + return md; + } + JavaType.Class exceptionType = JavaType.ShallowClass.build("java.lang.Exception"); + return md.withThrows(ListUtils.concat(md.getThrows(), + new J.Identifier(randomId(), Space.SINGLE_SPACE, Markers.EMPTY, emptyList(), + exceptionType.getClassName(), exceptionType, null))); + } +} diff --git a/src/main/resources/META-INF/rewrite/powermockito.yml b/src/main/resources/META-INF/rewrite/powermockito.yml index c45e62ff7..69b7a695d 100644 --- a/src/main/resources/META-INF/rewrite/powermockito.yml +++ b/src/main/resources/META-INF/rewrite/powermockito.yml @@ -59,3 +59,17 @@ recipeList: - org.openrewrite.maven.RemoveManagedDependency: groupId: org.powermock artifactId: powermock* +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.testing.mockito.PowerMockWhiteboxToJavaReflection +displayName: Replace PowerMock `Whitebox` with Java reflection +description: >- + Replace `org.powermock.reflect.Whitebox` calls (`setInternalState`, `getInternalState`, `invokeMethod`) + with plain Java reflection using `java.lang.reflect.Field` and `java.lang.reflect.Method`. +tags: + - testing + - mockito +recipeList: + - org.openrewrite.java.testing.mockito.PowerMockWhiteboxSetInternalStateToJavaReflection + - org.openrewrite.java.testing.mockito.PowerMockWhiteboxGetInternalStateToJavaReflection + - org.openrewrite.java.testing.mockito.PowerMockWhiteboxInvokeMethodToJavaReflection diff --git a/src/main/resources/META-INF/rewrite/recipes.csv b/src/main/resources/META-INF/rewrite/recipes.csv index c31a59384..704708aa1 100644 --- a/src/main/resources/META-INF/rewrite/recipes.csv +++ b/src/main/resources/META-INF/rewrite/recipes.csv @@ -201,7 +201,10 @@ maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.tes maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.RemoveInitMocksIfRunnersSpecified,Remove `MockitoAnnotations.initMocks(this)` and `openMocks(this)` if JUnit runners specified,Remove `MockitoAnnotations.initMocks(this)` and `MockitoAnnotations.openMocks(this)` if class-level JUnit runners `@RunWith(MockitoJUnitRunner.class)` or `@ExtendWith(MockitoExtension.class)` are specified. These manual initialization calls are redundant when using Mockito's JUnit integration. Note that the `@Mock` fields will then be initialized by the strict mocking session of the extension or runner; tests that relied on the lenient mocks created by an explicit `openMocks(this)` call inside `@BeforeEach` may surface `UnnecessaryStubbingException`. Add `@MockitoSettings(strictness = Strictness.LENIENT)` to opt out.,1,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.AddMockitoSettingsWithWarnStrictness,Add `@MockitoSettings(strictness = Strictness.WARN)` to `@ExtendWith(MockitoExtension.class)` classes,Adds `@MockitoSettings(strictness = Strictness.WARN)` to test classes that have `@ExtendWith(MockitoExtension.class)` but do not already have a `@MockitoSettings` annotation. This preserves the lenient stubbing behavior from Mockito 1.x/2.x migrations and prevents `UnnecessaryStubbingException` from strict stubbing defaults.,1,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.VerifyZeroToNoMoreInteractions,Replace `verifyZeroInteractions()` with `verifyNoMoreInteractions()`,Replaces `verifyZeroInteractions()` with `verifyNoMoreInteractions()` in Mockito tests when migration when using a Mockito version < 3.x.,1,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.PowerMockWhiteboxToJavaReflection,Replace PowerMock `Whitebox` with Java reflection,"Replace `org.powermock.reflect.Whitebox` calls (`setInternalState`, `getInternalState`, `invokeMethod`) with plain Java reflection using `java.lang.reflect.Field` and `java.lang.reflect.Method`.",1,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,, +maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.PowerMockWhiteboxToJavaReflection,Replace PowerMock `Whitebox` with Java reflection,"Replace `org.powermock.reflect.Whitebox` calls (`setInternalState`, `getInternalState`, `invokeMethod`) with plain Java reflection using `java.lang.reflect.Field` and `java.lang.reflect.Method`.",4,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,, +maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.PowerMockWhiteboxSetInternalStateToJavaReflection,Replace PowerMock `Whitebox.setInternalState()` with Java reflection,"Replace `Whitebox.setInternalState(Object, String, Object)` with `java.lang.reflect.Field` access. The field lookup uses `getDeclaredField` on the target object's class, which differs from PowerMock's class-hierarchy traversal for fields inherited from a superclass.",1,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,, +maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.PowerMockWhiteboxGetInternalStateToJavaReflection,Replace PowerMock `Whitebox.getInternalState()` with Java reflection,"Replace `Whitebox.getInternalState(Object, String)` with `java.lang.reflect.Field` access, casting to the declared result type where needed. The field lookup uses `getDeclaredField` on the target object's class, which differs from PowerMock's class-hierarchy traversal for fields inherited from a superclass.",1,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,, +maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.PowerMockWhiteboxInvokeMethodToJavaReflection,Replace PowerMock `Whitebox.invokeMethod()` with Java reflection,"Replace `Whitebox.invokeMethod(Object, String, ..)` with `java.lang.reflect.Method` lookup and `invoke()`. Parameter types are taken from the unambiguously resolved target method, falling back to each argument's compile-time class.",1,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.ArgumentMatcherToLambda,Convert `ArgumentMatcher` anonymous class to lambda,"Converts anonymous `ArgumentMatcher` implementations with `matches(Object)` to lambda expressions with the correct parameter type. In Mockito 1.x, `ArgumentMatcher` extended Hamcrest's `BaseMatcher` and `matches` always took `Object`. In Mockito 2+, `ArgumentMatcher` is a functional interface where `matches` takes `T`.",1,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.MockConstructionToTryWithResources,Wrap `MockedConstruction` in try-with-resources,"Wraps `MockedConstruction` variable declarations that have explicit `.close()` calls into try-with-resources blocks, removing the explicit close call. This ensures proper resource management and makes the code cleaner.",1,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-testing-frameworks,org.openrewrite.java.testing.mockito.RemoveDoNothingForDefaultMocks,Remove `doNothing()` for void methods on `@Mock` fields,"Remove unnecessary `doNothing()` stubbings for void methods on `@Mock` fields. Mockito mocks already do nothing for void methods by default, making these stubbings redundant and triggering strict stubbing violations in Mockito 3+.",1,Mockito,Testing,Java,,,Basic building blocks for transforming Java code.,, diff --git a/src/test/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxGetInternalStateToJavaReflectionTest.java b/src/test/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxGetInternalStateToJavaReflectionTest.java new file mode 100644 index 000000000..c1721c06f --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxGetInternalStateToJavaReflectionTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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 PowerMockWhiteboxGetInternalStateToJavaReflectionTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec + .parser(JavaParser.fromJavaVersion() + .logCompilationWarningsAndErrors(true) + .classpathFromResources(new InMemoryExecutionContext(), + "powermock-core-1", + "powermock-reflect-1" + )) + .recipe(new PowerMockWhiteboxGetInternalStateToJavaReflection()); + } + + @DocumentExample + @Test + void getInternalStateWithAssignment() { + //language=java + rewriteRun( + java( + """ + class MyService { + private String name = "hello"; + } + """ + ), + java( + """ + import org.powermock.reflect.Whitebox; + + class MyServiceTest { + void testGetField() { + MyService service = new MyService(); + String result = Whitebox.getInternalState(service, "name"); + } + } + """, + """ + import java.lang.reflect.Field; + + class MyServiceTest { + void testGetField() throws Exception { + MyService service = new MyService(); + Field nameField = service.getClass().getDeclaredField("name"); + nameField.setAccessible(true); + String result = (String) nameField.get(service); + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxInvokeMethodToJavaReflectionTest.java b/src/test/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxInvokeMethodToJavaReflectionTest.java new file mode 100644 index 000000000..8e1da38c3 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxInvokeMethodToJavaReflectionTest.java @@ -0,0 +1,283 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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 PowerMockWhiteboxInvokeMethodToJavaReflectionTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec + .parser(JavaParser.fromJavaVersion() + .logCompilationWarningsAndErrors(true) + .classpathFromResources(new InMemoryExecutionContext(), + "powermock-core-1", + "powermock-reflect-1" + )) + .recipe(new PowerMockWhiteboxInvokeMethodToJavaReflection()); + } + + @DocumentExample + @Test + void invokeMethodNoArgs() { + //language=java + rewriteRun( + java( + """ + class MyService { + private String compute() { return "result"; } + } + """ + ), + java( + """ + import org.powermock.reflect.Whitebox; + + class MyServiceTest { + void testInvoke() { + MyService service = new MyService(); + String result = Whitebox.invokeMethod(service, "compute"); + } + } + """, + """ + import java.lang.reflect.Method; + + class MyServiceTest { + void testInvoke() throws Exception { + MyService service = new MyService(); + Method computeMethod = service.getClass().getDeclaredMethod("compute"); + computeMethod.setAccessible(true); + String result = (String) computeMethod.invoke(service); + } + } + """ + ) + ); + } + + @Test + void invokeMethodWithArgs() { + //language=java + rewriteRun( + java( + """ + class MyService { + private String greet(String name) { return "Hello " + name; } + } + """ + ), + java( + """ + import org.powermock.reflect.Whitebox; + + class MyServiceTest { + void testInvokeWithArgs() { + MyService service = new MyService(); + String result = Whitebox.invokeMethod(service, "greet", "World"); + } + } + """, + """ + import java.lang.reflect.Method; + + class MyServiceTest { + void testInvokeWithArgs() throws Exception { + MyService service = new MyService(); + Method greetMethod = service.getClass().getDeclaredMethod("greet", String.class); + greetMethod.setAccessible(true); + String result = (String) greetMethod.invoke(service, "World"); + } + } + """ + ) + ); + } + + @Test + void invokeMethodWithMultipleArgs() { + //language=java + rewriteRun( + java( + """ + class MyService { + private String combine(String a, String b) { return a + b; } + } + """ + ), + java( + """ + import org.powermock.reflect.Whitebox; + + class MyServiceTest { + void testInvokeWithMultipleArgs() { + MyService service = new MyService(); + String result = Whitebox.invokeMethod(service, "combine", "Hello", "World"); + } + } + """, + """ + import java.lang.reflect.Method; + + class MyServiceTest { + void testInvokeWithMultipleArgs() throws Exception { + MyService service = new MyService(); + Method combineMethod = service.getClass().getDeclaredMethod("combine", String.class, String.class); + combineMethod.setAccessible(true); + String result = (String) combineMethod.invoke(service, "Hello", "World"); + } + } + """ + ) + ); + } + + @Test + void invokeMethodWithPrimitiveArg() { + //language=java + rewriteRun( + java( + """ + class MyService { + private int doubleIt(int value) { return value * 2; } + } + """ + ), + java( + """ + import org.powermock.reflect.Whitebox; + + class MyServiceTest { + void testInvokeWithPrimitive() { + MyService service = new MyService(); + Whitebox.invokeMethod(service, "doubleIt", 5); + } + } + """, + """ + import java.lang.reflect.Method; + + class MyServiceTest { + void testInvokeWithPrimitive() throws Exception { + MyService service = new MyService(); + Method doubleItMethod = service.getClass().getDeclaredMethod("doubleIt", int.class); + doubleItMethod.setAccessible(true); + doubleItMethod.invoke(service, 5); + } + } + """ + ) + ); + } + + @Test + void invokeMethodWithConcreteArgButInterfaceParam() { + //language=java + rewriteRun( + java( + """ + import java.util.List; + + class MyService { + private String process(List items) { return items.toString(); } + } + """ + ), + java( + """ + import java.util.ArrayList; + import org.powermock.reflect.Whitebox; + + class MyServiceTest { + void testInvokeWithConcreteArg() { + MyService service = new MyService(); + ArrayList items = new ArrayList<>(); + String result = Whitebox.invokeMethod(service, "process", items); + } + } + """, + """ + import java.lang.reflect.Method; + import java.util.ArrayList; + import java.util.List; + + class MyServiceTest { + void testInvokeWithConcreteArg() throws Exception { + MyService service = new MyService(); + ArrayList items = new ArrayList<>(); + Method processMethod = service.getClass().getDeclaredMethod("process", List.class); + processMethod.setAccessible(true); + String result = (String) processMethod.invoke(service, items); + } + } + """ + ) + ); + } + + @Test + void invokeMethodWithInterfaceTypedArg() { + //language=java + rewriteRun( + java( + """ + import java.util.List; + + class MyService { + private String process(List items) { return items.toString(); } + } + """ + ), + java( + """ + import java.util.ArrayList; + import java.util.List; + import org.powermock.reflect.Whitebox; + + class MyServiceTest { + void testInvokeWithInterfaceArg() { + MyService service = new MyService(); + List items = new ArrayList<>(); + String result = Whitebox.invokeMethod(service, "process", items); + } + } + """, + """ + import java.lang.reflect.Method; + import java.util.ArrayList; + import java.util.List; + + class MyServiceTest { + void testInvokeWithInterfaceArg() throws Exception { + MyService service = new MyService(); + List items = new ArrayList<>(); + Method processMethod = service.getClass().getDeclaredMethod("process", List.class); + processMethod.setAccessible(true); + String result = (String) processMethod.invoke(service, items); + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxSetInternalStateToJavaReflectionTest.java b/src/test/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxSetInternalStateToJavaReflectionTest.java new file mode 100644 index 000000000..9405ad775 --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxSetInternalStateToJavaReflectionTest.java @@ -0,0 +1,330 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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 PowerMockWhiteboxSetInternalStateToJavaReflectionTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec + .parser(JavaParser.fromJavaVersion() + .logCompilationWarningsAndErrors(true) + .classpathFromResources(new InMemoryExecutionContext(), + "powermock-core-1", + "powermock-reflect-1" + )) + .recipe(new PowerMockWhiteboxSetInternalStateToJavaReflection()); + } + + @DocumentExample + @Test + void setInternalStateReplacedWithReflection() { + //language=java + rewriteRun( + java( + """ + class MyService { + private String name; + } + """ + ), + java( + """ + import org.powermock.reflect.Whitebox; + + class MyServiceTest { + void testSetField() { + MyService service = new MyService(); + Whitebox.setInternalState(service, "name", "expectedValue"); + } + } + """, + """ + import java.lang.reflect.Field; + + class MyServiceTest { + void testSetField() throws Exception { + MyService service = new MyService(); + Field nameField = service.getClass().getDeclaredField("name"); + nameField.setAccessible(true); + nameField.set(service, "expectedValue"); + } + } + """ + ) + ); + } + + @Test + void setInternalStateWithWhereClass() { + //language=java + rewriteRun( + java( + """ + class Parent { + private String name; + } + """ + ), + java( + """ + class Child extends Parent { + } + """ + ), + java( + """ + import org.powermock.reflect.Whitebox; + + class MyServiceTest { + void test() { + Child child = new Child(); + Whitebox.setInternalState(child, "name", "newValue", Parent.class); + } + } + """, + """ + import java.lang.reflect.Field; + + class MyServiceTest { + void test() throws Exception { + Child child = new Child(); + Field nameField = Parent.class.getDeclaredField("name"); + nameField.setAccessible(true); + nameField.set(child, "newValue"); + } + } + """ + ) + ); + } + + @Test + void setInternalStateWithWhereClassVariable() { + //language=java + rewriteRun( + java( + """ + class Parent { + private String name; + } + """ + ), + java( + """ + class Child extends Parent { + } + """ + ), + java( + """ + import org.powermock.reflect.Whitebox; + + class MyServiceTest { + void test() { + Child child = new Child(); + Class where = Parent.class; + Whitebox.setInternalState(child, "name", "newValue", where); + } + } + """, + """ + import java.lang.reflect.Field; + + class MyServiceTest { + void test() throws Exception { + Child child = new Child(); + Class where = Parent.class; + Field nameField = where.getDeclaredField("name"); + nameField.setAccessible(true); + nameField.set(child, "newValue"); + } + } + """ + ) + ); + } + + @Test + void throwsExceptionNotDuplicatedWhenAlreadyPresent() { + //language=java + rewriteRun( + java( + """ + class MyService { + private String name; + } + """ + ), + java( + """ + import org.powermock.reflect.Whitebox; + + class MyServiceTest { + void testSetField() throws Exception { + MyService service = new MyService(); + Whitebox.setInternalState(service, "name", "expectedValue"); + } + } + """, + """ + import java.lang.reflect.Field; + + class MyServiceTest { + void testSetField() throws Exception { + MyService service = new MyService(); + Field nameField = service.getClass().getDeclaredField("name"); + nameField.setAccessible(true); + nameField.set(service, "expectedValue"); + } + } + """ + ) + ); + } + + @Test + void whiteboxInsideIfBlock() { + //language=java + rewriteRun( + java( + """ + class MyService { + private String name; + } + """ + ), + java( + """ + import org.powermock.reflect.Whitebox; + + class MyServiceTest { + void testSetFieldConditionally(boolean condition) { + MyService service = new MyService(); + if (condition) { + Whitebox.setInternalState(service, "name", "expectedValue"); + } + } + } + """, + """ + import java.lang.reflect.Field; + + class MyServiceTest { + void testSetFieldConditionally(boolean condition) throws Exception { + MyService service = new MyService(); + if (condition) { + Field nameField = service.getClass().getDeclaredField("name"); + nameField.setAccessible(true); + nameField.set(service, "expectedValue"); + } + } + } + """ + ) + ); + } + + @Test + void multipleWhiteboxCallsSameFieldName() { + //language=java + rewriteRun( + java( + """ + class MyService { + private String name; + } + """ + ), + java( + """ + import org.powermock.reflect.Whitebox; + + class MyServiceTest { + void testSetFieldTwice() { + MyService svc1 = new MyService(); + MyService svc2 = new MyService(); + Whitebox.setInternalState(svc1, "name", "first"); + Whitebox.setInternalState(svc2, "name", "second"); + } + } + """, + """ + import java.lang.reflect.Field; + + class MyServiceTest { + void testSetFieldTwice() throws Exception { + MyService svc1 = new MyService(); + MyService svc2 = new MyService(); + Field nameField1 = svc1.getClass().getDeclaredField("name"); + nameField1.setAccessible(true); + nameField1.set(svc1, "first"); + Field nameField = svc2.getClass().getDeclaredField("name"); + nameField.setAccessible(true); + nameField.set(svc2, "second"); + } + } + """ + ) + ); + } + + @Test + void throwsNotAddedWhenThrowableAlreadyPresent() { + //language=java + rewriteRun( + java( + """ + class MyService { + private String name; + } + """ + ), + java( + """ + import org.powermock.reflect.Whitebox; + + class MyServiceTest { + void testSetField() throws Throwable { + MyService service = new MyService(); + Whitebox.setInternalState(service, "name", "expectedValue"); + } + } + """, + """ + import java.lang.reflect.Field; + + class MyServiceTest { + void testSetField() throws Throwable { + MyService service = new MyService(); + Field nameField = service.getClass().getDeclaredField("name"); + nameField.setAccessible(true); + nameField.set(service, "expectedValue"); + } + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxToJavaReflectionTest.java b/src/test/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxToJavaReflectionTest.java index a09bfd227..8f3d75895 100644 --- a/src/test/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxToJavaReflectionTest.java +++ b/src/test/java/org/openrewrite/java/testing/mockito/PowerMockWhiteboxToJavaReflectionTest.java @@ -34,7 +34,7 @@ public void defaults(RecipeSpec spec) { "powermock-core-1", "powermock-reflect-1" )) - .recipe(new PowerMockWhiteboxToJavaReflection()); + .recipeFromResources("org.openrewrite.java.testing.mockito.PowerMockWhiteboxToJavaReflection"); } @DocumentExample @@ -167,13 +167,15 @@ void test() throws Exception { } @Test - void getInternalStateWithAssignment() { + void migratesSetGetAndInvokeInOneMethod() { //language=java rewriteRun( java( """ class MyService { - private String name = "hello"; + private String name; + private String description = "d"; + private String compute() { return name; } } """ ), @@ -182,56 +184,27 @@ class MyService { import org.powermock.reflect.Whitebox; class MyServiceTest { - void testGetField() { + void test() { MyService service = new MyService(); - String result = Whitebox.getInternalState(service, "name"); + Whitebox.setInternalState(service, "name", "newValue"); + String desc = Whitebox.getInternalState(service, "description"); + String result = Whitebox.invokeMethod(service, "compute"); } } """, """ import java.lang.reflect.Field; + import java.lang.reflect.Method; class MyServiceTest { - void testGetField() throws Exception { + void test() throws Exception { MyService service = new MyService(); Field nameField = service.getClass().getDeclaredField("name"); nameField.setAccessible(true); - String result = (String) nameField.get(service); - } - } - """ - ) - ); - } - - @Test - void invokeMethodNoArgs() { - //language=java - rewriteRun( - java( - """ - class MyService { - private String compute() { return "result"; } - } - """ - ), - java( - """ - import org.powermock.reflect.Whitebox; - - class MyServiceTest { - void testInvoke() { - MyService service = new MyService(); - String result = Whitebox.invokeMethod(service, "compute"); - } - } - """, - """ - import java.lang.reflect.Method; - - class MyServiceTest { - void testInvoke() throws Exception { - MyService service = new MyService(); + nameField.set(service, "newValue"); + Field descriptionField = service.getClass().getDeclaredField("description"); + descriptionField.setAccessible(true); + String desc = (String) descriptionField.get(service); Method computeMethod = service.getClass().getDeclaredMethod("compute"); computeMethod.setAccessible(true); String result = (String) computeMethod.invoke(service); @@ -242,373 +215,6 @@ void testInvoke() throws Exception { ); } - @Test - void invokeMethodWithArgs() { - //language=java - rewriteRun( - java( - """ - class MyService { - private String greet(String name) { return "Hello " + name; } - } - """ - ), - java( - """ - import org.powermock.reflect.Whitebox; - - class MyServiceTest { - void testInvokeWithArgs() { - MyService service = new MyService(); - String result = Whitebox.invokeMethod(service, "greet", "World"); - } - } - """, - """ - import java.lang.reflect.Method; - - class MyServiceTest { - void testInvokeWithArgs() throws Exception { - MyService service = new MyService(); - Method greetMethod = service.getClass().getDeclaredMethod("greet", String.class); - greetMethod.setAccessible(true); - String result = (String) greetMethod.invoke(service, "World"); - } - } - """ - ) - ); - } - - @Test - void invokeMethodWithMultipleArgs() { - //language=java - rewriteRun( - java( - """ - class MyService { - private String combine(String a, String b) { return a + b; } - } - """ - ), - java( - """ - import org.powermock.reflect.Whitebox; - - class MyServiceTest { - void testInvokeWithMultipleArgs() { - MyService service = new MyService(); - String result = Whitebox.invokeMethod(service, "combine", "Hello", "World"); - } - } - """, - """ - import java.lang.reflect.Method; - - class MyServiceTest { - void testInvokeWithMultipleArgs() throws Exception { - MyService service = new MyService(); - Method combineMethod = service.getClass().getDeclaredMethod("combine", String.class, String.class); - combineMethod.setAccessible(true); - String result = (String) combineMethod.invoke(service, "Hello", "World"); - } - } - """ - ) - ); - } - - @Test - void invokeMethodWithPrimitiveArg() { - //language=java - rewriteRun( - java( - """ - class MyService { - private int doubleIt(int value) { return value * 2; } - } - """ - ), - java( - """ - import org.powermock.reflect.Whitebox; - - class MyServiceTest { - void testInvokeWithPrimitive() { - MyService service = new MyService(); - Whitebox.invokeMethod(service, "doubleIt", 5); - } - } - """, - """ - import java.lang.reflect.Method; - - class MyServiceTest { - void testInvokeWithPrimitive() throws Exception { - MyService service = new MyService(); - Method doubleItMethod = service.getClass().getDeclaredMethod("doubleIt", int.class); - doubleItMethod.setAccessible(true); - doubleItMethod.invoke(service, 5); - } - } - """ - ) - ); - } - - @Test - void invokeMethodWithConcreteArgButInterfaceParam() { - //language=java - rewriteRun( - java( - """ - import java.util.List; - - class MyService { - private String process(List items) { return items.toString(); } - } - """ - ), - java( - """ - import java.util.ArrayList; - import org.powermock.reflect.Whitebox; - - class MyServiceTest { - void testInvokeWithConcreteArg() { - MyService service = new MyService(); - ArrayList items = new ArrayList<>(); - String result = Whitebox.invokeMethod(service, "process", items); - } - } - """, - """ - import java.lang.reflect.Method; - import java.util.ArrayList; - import java.util.List; - - class MyServiceTest { - void testInvokeWithConcreteArg() throws Exception { - MyService service = new MyService(); - ArrayList items = new ArrayList<>(); - Method processMethod = service.getClass().getDeclaredMethod("process", List.class); - processMethod.setAccessible(true); - String result = (String) processMethod.invoke(service, items); - } - } - """ - ) - ); - } - - @Test - void invokeMethodWithInterfaceTypedArg() { - //language=java - rewriteRun( - java( - """ - import java.util.List; - - class MyService { - private String process(List items) { return items.toString(); } - } - """ - ), - java( - """ - import java.util.ArrayList; - import java.util.List; - import org.powermock.reflect.Whitebox; - - class MyServiceTest { - void testInvokeWithInterfaceArg() { - MyService service = new MyService(); - List items = new ArrayList<>(); - String result = Whitebox.invokeMethod(service, "process", items); - } - } - """, - """ - import java.lang.reflect.Method; - import java.util.ArrayList; - import java.util.List; - - class MyServiceTest { - void testInvokeWithInterfaceArg() throws Exception { - MyService service = new MyService(); - List items = new ArrayList<>(); - Method processMethod = service.getClass().getDeclaredMethod("process", List.class); - processMethod.setAccessible(true); - String result = (String) processMethod.invoke(service, items); - } - } - """ - ) - ); - } - - @Test - void throwsExceptionNotDuplicatedWhenAlreadyPresent() { - //language=java - rewriteRun( - java( - """ - class MyService { - private String name; - } - """ - ), - java( - """ - import org.powermock.reflect.Whitebox; - - class MyServiceTest { - void testSetField() throws Exception { - MyService service = new MyService(); - Whitebox.setInternalState(service, "name", "expectedValue"); - } - } - """, - """ - import java.lang.reflect.Field; - - class MyServiceTest { - void testSetField() throws Exception { - MyService service = new MyService(); - Field nameField = service.getClass().getDeclaredField("name"); - nameField.setAccessible(true); - nameField.set(service, "expectedValue"); - } - } - """ - ) - ); - } - - @Test - void whiteboxInsideIfBlock() { - //language=java - rewriteRun( - java( - """ - class MyService { - private String name; - } - """ - ), - java( - """ - import org.powermock.reflect.Whitebox; - - class MyServiceTest { - void testSetFieldConditionally(boolean condition) { - MyService service = new MyService(); - if (condition) { - Whitebox.setInternalState(service, "name", "expectedValue"); - } - } - } - """, - """ - import java.lang.reflect.Field; - - class MyServiceTest { - void testSetFieldConditionally(boolean condition) throws Exception { - MyService service = new MyService(); - if (condition) { - Field nameField = service.getClass().getDeclaredField("name"); - nameField.setAccessible(true); - nameField.set(service, "expectedValue"); - } - } - } - """ - ) - ); - } - - @Test - void multipleWhiteboxCallsSameFieldName() { - //language=java - rewriteRun( - java( - """ - class MyService { - private String name; - } - """ - ), - java( - """ - import org.powermock.reflect.Whitebox; - - class MyServiceTest { - void testSetFieldTwice() { - MyService svc1 = new MyService(); - MyService svc2 = new MyService(); - Whitebox.setInternalState(svc1, "name", "first"); - Whitebox.setInternalState(svc2, "name", "second"); - } - } - """, - """ - import java.lang.reflect.Field; - - class MyServiceTest { - void testSetFieldTwice() throws Exception { - MyService svc1 = new MyService(); - MyService svc2 = new MyService(); - Field nameField1 = svc1.getClass().getDeclaredField("name"); - nameField1.setAccessible(true); - nameField1.set(svc1, "first"); - Field nameField = svc2.getClass().getDeclaredField("name"); - nameField.setAccessible(true); - nameField.set(svc2, "second"); - } - } - """ - ) - ); - } - - @Test - void throwsNotAddedWhenThrowableAlreadyPresent() { - //language=java - rewriteRun( - java( - """ - class MyService { - private String name; - } - """ - ), - java( - """ - import org.powermock.reflect.Whitebox; - - class MyServiceTest { - void testSetField() throws Throwable { - MyService service = new MyService(); - Whitebox.setInternalState(service, "name", "expectedValue"); - } - } - """, - """ - import java.lang.reflect.Field; - - class MyServiceTest { - void testSetField() throws Throwable { - MyService service = new MyService(); - Field nameField = service.getClass().getDeclaredField("name"); - nameField.setAccessible(true); - nameField.set(service, "expectedValue"); - } - } - """ - ) - ); - } - @Test void noChangeWhenWhiteboxNotUsed() { //language=java