diff --git a/spring-test/src/main/java/org/springframework/test/util/ReflectionTestUtils.java b/spring-test/src/main/java/org/springframework/test/util/ReflectionTestUtils.java
index f922c8a34a4..42eeb706cbd 100644
--- a/spring-test/src/main/java/org/springframework/test/util/ReflectionTestUtils.java
+++ b/spring-test/src/main/java/org/springframework/test/util/ReflectionTestUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2017 the original author or authors.
+ * Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -403,14 +403,65 @@ public abstract class ReflectionTestUtils {
/**
* Invoke the method with the given {@code name} on the supplied target
* object with the supplied arguments.
- *
This method traverses the class hierarchy in search of the desired
- * method. In addition, an attempt will be made to make non-{@code public}
- * methods accessible, thus allowing one to invoke {@code protected},
- * {@code private}, and package-private methods.
+ *
This method delegates to {@link #invokeMethod(Object, Class, String, Object...)},
+ * supplying {@code null} for the {@code targetClass} argument.
* @param target the target object on which to invoke the specified method
* @param name the name of the method to invoke
* @param args the arguments to provide to the method
* @return the invocation result, if any
+ * @see #invokeMethod(Class, String, Object...)
+ * @see #invokeMethod(Object, Class, String, Object...)
+ * @see MethodInvoker
+ * @see ReflectionUtils#makeAccessible(Method)
+ * @see ReflectionUtils#invokeMethod(Method, Object, Object[])
+ * @see ReflectionUtils#handleReflectionException(Exception)
+ */
+ @Nullable
+ public static T invokeMethod(Object target, String name, Object... args) {
+ Assert.notNull(target, "Target object must not be null");
+ return invokeMethod(target, null, name, args);
+ }
+
+ /**
+ * Invoke the static method with the given {@code name} on the supplied target
+ * class with the supplied arguments.
+ * This method delegates to {@link #invokeMethod(Object, Class, String, Object...)},
+ * supplying {@code null} for the {@code targetObject} argument.
+ * @param targetClass the target class on which to invoke the specified method
+ * @param name the name of the method to invoke
+ * @param args the arguments to provide to the method
+ * @return the invocation result, if any
+ * @since 5.2
+ * @see #invokeMethod(Object, String, Object...)
+ * @see #invokeMethod(Object, Class, String, Object...)
+ * @see MethodInvoker
+ * @see ReflectionUtils#makeAccessible(Method)
+ * @see ReflectionUtils#invokeMethod(Method, Object, Object[])
+ * @see ReflectionUtils#handleReflectionException(Exception)
+ */
+ @Nullable
+ public static T invokeMethod(Class> targetClass, String name, Object... args) {
+ Assert.notNull(targetClass, "Target class must not be null");
+ return invokeMethod(null, targetClass, name, args);
+ }
+
+ /**
+ * Invoke the method with the given {@code name} on the provided
+ * {@code targetObject}/{@code targetClass} with the supplied arguments.
+ * This method traverses the class hierarchy in search of the desired
+ * method. In addition, an attempt will be made to make non-{@code public}
+ * methods accessible, thus allowing one to invoke {@code protected},
+ * {@code private}, and package-private methods.
+ * @param targetObject the target object on which to invoke the method; may
+ * be {@code null} if the method is static
+ * @param targetClass the target class on which to invoke the method; may
+ * be {@code null} if the method is an instance method
+ * @param name the name of the method to invoke
+ * @param args the arguments to provide to the method
+ * @return the invocation result, if any
+ * @since 5.2
+ * @see #invokeMethod(Object, String, Object...)
+ * @see #invokeMethod(Class, String, Object...)
* @see MethodInvoker
* @see ReflectionUtils#makeAccessible(Method)
* @see ReflectionUtils#invokeMethod(Method, Object, Object[])
@@ -418,20 +469,26 @@ public abstract class ReflectionTestUtils {
*/
@SuppressWarnings("unchecked")
@Nullable
- public static T invokeMethod(Object target, String name, Object... args) {
- Assert.notNull(target, "Target object must not be null");
+ public static T invokeMethod(@Nullable Object targetObject, @Nullable Class> targetClass, String name,
+ Object... args) {
+
+ Assert.isTrue(targetObject != null || targetClass != null,
+ "Either 'targetObject' or 'targetClass' for the method must be specified");
Assert.hasText(name, "Method name must not be empty");
try {
MethodInvoker methodInvoker = new MethodInvoker();
- methodInvoker.setTargetObject(target);
+ methodInvoker.setTargetObject(targetObject);
+ if (targetClass != null) {
+ methodInvoker.setTargetClass(targetClass);
+ }
methodInvoker.setTargetMethod(name);
methodInvoker.setArguments(args);
methodInvoker.prepare();
if (logger.isDebugEnabled()) {
- logger.debug(String.format("Invoking method '%s' on %s with arguments %s", name, safeToString(target),
- ObjectUtils.nullSafeToString(args)));
+ logger.debug(String.format("Invoking method '%s' on %s or %s with arguments %s", name,
+ safeToString(targetObject), safeToString(targetClass), ObjectUtils.nullSafeToString(args)));
}
return (T) methodInvoker.invoke();
@@ -452,4 +509,8 @@ public abstract class ReflectionTestUtils {
}
}
+ private static String safeToString(@Nullable Class> clazz) {
+ return String.format("target class [%s]", (clazz != null ? clazz.getName() : null));
+ }
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java b/spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java
index 8261183dbf4..cdc504961db 100644
--- a/spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java
+++ b/spring-test/src/test/java/org/springframework/test/util/ReflectionTestUtilsTests.java
@@ -27,6 +27,7 @@ import org.springframework.test.util.subpackage.LegacyEntity;
import org.springframework.test.util.subpackage.Person;
import org.springframework.test.util.subpackage.PersonEntity;
import org.springframework.test.util.subpackage.StaticFields;
+import org.springframework.test.util.subpackage.StaticMethods;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -57,6 +58,7 @@ class ReflectionTestUtilsTests {
@BeforeEach
void resetStaticFields() {
StaticFields.reset();
+ StaticMethods.reset();
}
@Test
@@ -327,7 +329,7 @@ class ReflectionTestUtilsTests {
}
@Test
- @Disabled("[SPR-8644] findMethod() does not currently support var-args")
+ @Disabled("[SPR-8644] MethodInvoker.findMatchingMethod() does not currently support var-args")
void invokeMethodWithPrimitiveVarArgs() {
// IntelliJ IDEA 11 won't accept int assignment here
Integer sum = invokeMethod(component, "add", 1, 2, 3, 4);
@@ -422,4 +424,66 @@ class ReflectionTestUtilsTests {
assertThat(entity.toString().contains(testCollaborator)).isTrue();
}
+ @Test
+ void invokeStaticMethodWithNullTargetClass() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> invokeMethod((Class>) null, null))
+ .withMessage("Target class must not be null");
+ }
+
+ @Test
+ void invokeStaticMethodWithNullMethodName() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> invokeMethod(getClass(), null))
+ .withMessage("Method name must not be empty");
+ }
+
+ @Test
+ void invokeStaticMethodWithEmptyMethodName() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> invokeMethod(getClass(), " "))
+ .withMessage("Method name must not be empty");
+ }
+
+ @Test
+ void invokePublicStaticVoidMethodWithArguments() {
+ assertThat(StaticMethods.getPublicMethodValue()).isEqualTo("public");
+
+ String testCollaborator = "test collaborator";
+ invokeMethod(StaticMethods.class, "publicMethod", testCollaborator);
+ assertThat(StaticMethods.getPublicMethodValue()).isEqualTo(testCollaborator);
+ }
+
+ @Test
+ void invokePublicStaticMethodWithoutArguments() {
+ assertThat(StaticMethods.getPublicMethodValue()).isEqualTo("public");
+
+ String result = invokeMethod(StaticMethods.class, "publicMethod");
+ assertThat(result).isEqualTo(StaticMethods.getPublicMethodValue());
+ }
+
+ @Test
+ void invokePrivateStaticVoidMethodWithArguments() {
+ assertThat(StaticMethods.getPrivateMethodValue()).isEqualTo("private");
+
+ String testCollaborator = "test collaborator";
+ invokeMethod(StaticMethods.class, "privateMethod", testCollaborator);
+ assertThat(StaticMethods.getPrivateMethodValue()).isEqualTo(testCollaborator);
+ }
+
+ @Test
+ void invokePrivateStaticMethodWithoutArguments() {
+ assertThat(StaticMethods.getPrivateMethodValue()).isEqualTo("private");
+
+ String result = invokeMethod(StaticMethods.class, "privateMethod");
+ assertThat(result).isEqualTo(StaticMethods.getPrivateMethodValue());
+ }
+
+ @Test
+ void invokeStaticMethodWithNullTargetObjectAndNullTargetClass() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> invokeMethod((Object) null, (Class>) null, "id"))
+ .withMessage("Either 'targetObject' or 'targetClass' for the method must be specified");
+ }
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/util/subpackage/StaticMethods.java b/spring-test/src/test/java/org/springframework/test/util/subpackage/StaticMethods.java
new file mode 100644
index 00000000000..c1006e8e145
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/util/subpackage/StaticMethods.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2002-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.util.subpackage;
+
+/**
+ * Simple class with static methods; intended for use in unit tests.
+ *
+ * @author Sam Brannen
+ * @since 5.2
+ */
+public class StaticMethods {
+
+ public static String publicMethodValue = "public";
+
+ private static String privateMethodValue = "private";
+
+
+ public static void publicMethod(String value) {
+ publicMethodValue = value;
+ }
+
+ public static String publicMethod() {
+ return publicMethodValue;
+ }
+
+ @SuppressWarnings("unused")
+ private static void privateMethod(String value) {
+ privateMethodValue = value;
+ }
+
+ @SuppressWarnings("unused")
+ private static String privateMethod() {
+ return privateMethodValue;
+ }
+
+ public static void reset() {
+ publicMethodValue = "public";
+ privateMethodValue = "private";
+ }
+
+ public static String getPublicMethodValue() {
+ return publicMethodValue;
+ }
+
+ public static String getPrivateMethodValue() {
+ return privateMethodValue;
+ }
+
+}