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, ExecutionContext> 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, ExecutionContext> 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, ExecutionContext> 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, ExecutionContext> 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, ExecutionContext> withPrecondition() {
+ TreeVisitor, ExecutionContext> 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