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 9f1e77518e7a..7eb67409d659 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-2012 the original author or authors. + * Copyright 2002-2015 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. @@ -51,6 +51,10 @@ * methods. * * + *

In addition, several methods in this class provide support for {@code static} + * fields — for example, {@link #setField(Class, String, Object)}, + * {@link #getField(Class, String)}, etc. + * * @author Sam Brannen * @author Juergen Hoeller * @since 2.5 @@ -66,87 +70,204 @@ public class ReflectionTestUtils { /** - * Set the {@link Field field} with the given {@code name} on the provided - * {@link Object target object} to the supplied {@code value}. + * Set the {@linkplain Field field} with the given {@code name} on the + * provided {@code targetObject} to the supplied {@code value}. * - *

This method traverses the class hierarchy in search of the desired field. - * In addition, an attempt will be made to make non-{@code public} fields - * accessible, thus allowing one to set {@code protected}, - * {@code private}, and package-private fields. + *

This method delegates to {@link #setField(Object, String, Object, Class)}, + * supplying {@code null} for the {@code type} argument. * - * @param target the target object on which to set the field - * @param name the name of the field to set + * @param targetObject the target object on which to set the field; never {@code null} + * @param name the name of the field to set; never {@code null} * @param value the value to set - * @see ReflectionUtils#findField(Class, String, Class) - * @see ReflectionUtils#makeAccessible(Field) - * @see ReflectionUtils#setField(Field, Object, Object) */ - public static void setField(Object target, String name, Object value) { - setField(target, name, value, null); + public static void setField(Object targetObject, String name, Object value) { + setField(targetObject, name, value, null); + } + + /** + * Set the {@linkplain Field field} with the given {@code name}/{@code type} + * on the provided {@code targetObject} to the supplied {@code value}. + * + *

This method delegates to {@link #setField(Object, Class, String, Object, Class)}, + * supplying {@code null} for the {@code targetClass} argument. + * + * @param targetObject the target object on which to set the field; never {@code null} + * @param name the name of the field to set; may be {@code null} if + * {@code type} is specified + * @param value the value to set + * @param type the type of the field to set; may be {@code null} if + * {@code name} is specified + */ + public static void setField(Object targetObject, String name, Object value, Class type) { + setField(targetObject, null, name, value, type); + } + + /** + * Set the static {@linkplain Field field} with the given {@code name} on + * the provided {@code targetClass} to the supplied {@code value}. + * + *

This method delegates to {@link #setField(Object, Class, String, Object, Class)}, + * supplying {@code null} for the {@code targetObject} and {@code type} arguments. + * + * @param targetClass the target class on which to set the static field; + * never {@code null} + * @param name the name of the field to set; never {@code null} + * @param value the value to set + * @since 4.2 + */ + public static void setField(Class targetClass, String name, Object value) { + setField(null, targetClass, name, value, null); + } + + /** + * Set the static {@linkplain Field field} with the given + * {@code name}/{@code type} on the provided {@code targetClass} to + * the supplied {@code value}. + * + *

This method delegates to {@link #setField(Object, Class, String, Object, Class)}, + * supplying {@code null} for the {@code targetObject} argument. + * + * @param targetClass the target class on which to set the static field; + * never {@code null} + * @param name the name of the field to set; may be {@code null} if + * {@code type} is specified + * @param value the value to set + * @param type the type of the field to set; may be {@code null} if + * {@code name} is specified + * @since 4.2 + */ + public static void setField(Class targetClass, String name, Object value, Class type) { + setField(null, targetClass, name, value, type); } /** - * Set the {@link Field field} with the given {@code name} on the provided - * {@link Object target object} to the supplied {@code value}. + * Set the {@linkplain Field field} with the given {@code name}/{@code type} + * on the provided {@code targetObject}/{@code targetClass} to the supplied + * {@code value}. * *

This method traverses the class hierarchy in search of the desired * field. In addition, an attempt will be made to make non-{@code public} * fields accessible, thus allowing one to set {@code protected}, * {@code private}, and package-private fields. * - * @param target the target object on which to set the field - * @param name the name of the field to set + * @param targetObject the target object on which to set the field; may be + * {@code null} if the field is static + * @param targetClass the target class on which to set the field; may + * be {@code null} if the field is an instance field + * @param name the name of the field to set; may be {@code null} if + * {@code type} is specified * @param value the value to set - * @param type the type of the field (may be {@code null}) + * @param type the type of the field to set; may be {@code null} if + * {@code name} is specified * @see ReflectionUtils#findField(Class, String, Class) * @see ReflectionUtils#makeAccessible(Field) * @see ReflectionUtils#setField(Field, Object, Object) + * @since 4.2 */ - public static void setField(Object target, String name, Object value, Class type) { - Assert.notNull(target, "Target object must not be null"); - Field field = ReflectionUtils.findField(target.getClass(), name, type); + public static void setField(Object targetObject, Class targetClass, String name, Object value, Class type) { + Assert.isTrue(targetObject != null || targetClass != null, + "Either targetObject or targetClass for the field must be specified"); + + if (targetClass == null) { + targetClass = targetObject.getClass(); + } + Field field = ReflectionUtils.findField(targetClass, name, type); - // SPR-9571: inline Assert.notNull() in order to avoid accidentally invoking - // toString() on a non-null target. + // Inline Assert.notNull() to avoid invoking toString() on a non-null target. if (field == null) { - throw new IllegalArgumentException(String.format("Could not find field [%s] of type [%s] on target [%s]", - name, type, target)); + throw new IllegalArgumentException(String.format( + "Could not find field [%s] of type [%s] on target object [%s] or target class [%s]", name, type, + targetObject, targetClass)); } if (logger.isDebugEnabled()) { - logger.debug(String.format("Setting field [%s] of type [%s] on target [%s] to value [%s]", name, type, - target, - value)); + logger.debug(String.format( + "Setting field [%s] of type [%s] on target object [%s] or target class [%s] to value [%s]", name, type, + targetObject, targetClass, value)); } ReflectionUtils.makeAccessible(field); - ReflectionUtils.setField(field, target, value); + ReflectionUtils.setField(field, targetObject, value); } /** - * Get the field with the given {@code name} from the provided target object. + * Get the value of the {@linkplain Field field} with the given {@code name} + * from the provided {@code targetObject}. + * + *

This method delegates to {@link #getField(Object, Class, String)}, + * supplying {@code null} for the {@code targetClass} argument. + * + * @param targetObject the target object from which to get the field; + * never {@code null} + * @param name the name of the field to get; never {@code null} + * @return the field's current value + * @see #getField(Class, String) + */ + public static Object getField(Object targetObject, String name) { + return getField(targetObject, null, name); + } + + /** + * Get the value of the static {@linkplain Field field} with the given + * {@code name} from the provided {@code targetClass}. + * + *

This method delegates to {@link #getField(Object, Class, String)}, + * supplying {@code null} for the {@code targetObject} argument. + * + * @param targetClass the target class from which to get the static field; + * never {@code null} + * @param name the name of the field to get; never {@code null} + * @return the field's current value + * @see #getField(Object, String) + * @since 4.2 + */ + public static Object getField(Class targetClass, String name) { + return getField(null, targetClass, name); + } + + /** + * Get the value of the {@linkplain Field field} with the given {@code name} + * from the provided {@code targetObject}/{@code targetClass}. * *

This method traverses the class hierarchy in search of the desired * field. In addition, an attempt will be made to make non-{@code public} * fields accessible, thus allowing one to get {@code protected}, * {@code private}, and package-private fields. * - * @param target the target object on which to set the field - * @param name the name of the field to get + * @param targetObject the target object from which to get the field; may be + * {@code null} if the field is static + * @param targetClass the target class from which to get the field; may + * be {@code null} if the field is an instance field + * @param name the name of the field to get; never {@code null} * @return the field's current value + * @see #getField(Object, String) + * @see #getField(Class, String) * @see ReflectionUtils#findField(Class, String, Class) * @see ReflectionUtils#makeAccessible(Field) - * @see ReflectionUtils#setField(Field, Object, Object) + * @see ReflectionUtils#getField(Field, Object, Object) + * @since 4.2 */ - public static Object getField(Object target, String name) { - Assert.notNull(target, "Target object must not be null"); - Field field = ReflectionUtils.findField(target.getClass(), name); - Assert.notNull(field, "Could not find field [" + name + "] on target [" + target + "]"); + public static Object getField(Object targetObject, Class targetClass, String name) { + Assert.isTrue(targetObject != null || targetClass != null, + "Either targetObject or targetClass for the field must be specified"); + + if (targetClass == null) { + targetClass = targetObject.getClass(); + } + Field field = ReflectionUtils.findField(targetClass, name); + + // Inline Assert.notNull() to avoid invoking toString() on a non-null target. + if (field == null) { + throw new IllegalArgumentException( + String.format("Could not find field [%s] on target object [%s] or target class [%s]", name, + targetObject, targetClass)); + } if (logger.isDebugEnabled()) { - logger.debug("Getting field [" + name + "] from target [" + target + "]"); + logger.debug(String.format("Getting field [%s] from target object [%s] or target class [%s]", name, + targetObject, targetClass)); } ReflectionUtils.makeAccessible(field); - return ReflectionUtils.getField(field, target); + return ReflectionUtils.getField(field, targetObject); } /** 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 c9782d6b9149..43d7b3014a6b 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -16,13 +16,18 @@ package org.springframework.test.util; +import org.junit.Before; import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.springframework.test.util.subpackage.Component; import org.springframework.test.util.subpackage.LegacyEntity; import org.springframework.test.util.subpackage.Person; +import org.springframework.test.util.subpackage.StaticFields; +import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import static org.springframework.test.util.ReflectionTestUtils.*; @@ -39,9 +44,66 @@ public class ReflectionTestUtilsTests { private final Person person = new Person(); private final Component component = new Component(); + @Rule + public ExpectedException exception = ExpectedException.none(); + + + @Before + public void resetStaticFields() { + StaticFields.reset(); + } + + @Test + public void setFieldWithNullTargetObject() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage(startsWith("Either targetObject or targetClass")); + setField((Object) null, "id", new Long(99)); + } + + @Test + public void getFieldWithNullTargetObject() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage(startsWith("Either targetObject or targetClass")); + getField((Object) null, "id"); + } + + @Test + public void setFieldWithNullTargetClass() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage(startsWith("Either targetObject or targetClass")); + setField((Class) null, "id", new Long(99)); + } + + @Test + public void getFieldWithNullTargetClass() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage(startsWith("Either targetObject or targetClass")); + getField((Class) null, "id"); + } @Test - public void setFieldForStandardUseCases() throws Exception { + public void setFieldWithNullNameAndNullType() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage(startsWith("Either name or type")); + setField(person, null, new Long(99), null); + } + + @Test + public void setFieldWithBogusName() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage(startsWith("Could not find field [bogus]")); + setField(person, "bogus", new Long(99), long.class); + } + + @Test + public void setFieldWithWrongType() throws Exception { + exception.expect(IllegalArgumentException.class); + exception.expectMessage(startsWith("Could not find field")); + setField(person, "id", new Long(99), String.class); + } + + @Test + public void setFieldAndGetFieldForStandardUseCases() throws Exception { setField(person, "id", new Long(99), long.class); setField(person, "name", "Tom"); setField(person, "age", new Integer(42)); @@ -66,6 +128,15 @@ public void setFieldForStandardUseCases() throws Exception { @Test public void setFieldWithNullValuesForNonPrimitives() throws Exception { + // Fields must be non-null to start with + setField(person, "name", "Tom"); + setField(person, "eyeColor", "blue", String.class); + setField(person, "favoriteNumber", PI, Number.class); + assertNotNull(person.getName()); + assertNotNull(person.getEyeColor()); + assertNotNull(person.getFavoriteNumber()); + + // Set to null setField(person, "name", null, String.class); setField(person, "eyeColor", null, String.class); setField(person, "favoriteNumber", null, Number.class); @@ -102,7 +173,48 @@ public void setFieldOnLegacyEntityWithSideEffectsInToString() { } @Test - public void invokeSetterMethodWithExplicitSetterMethodNames() throws Exception { + public void setStaticFieldViaClass() throws Exception { + setField(StaticFields.class, "publicField", "xxx"); + setField(StaticFields.class, "privateField", "yyy"); + + assertEquals("public static field", "xxx", StaticFields.publicField); + assertEquals("private static field", "yyy", StaticFields.getPrivateField()); + } + + @Test + public void setStaticFieldViaClassWithExplicitType() throws Exception { + setField(StaticFields.class, "publicField", "xxx", String.class); + setField(StaticFields.class, "privateField", "yyy", String.class); + + assertEquals("public static field", "xxx", StaticFields.publicField); + assertEquals("private static field", "yyy", StaticFields.getPrivateField()); + } + + @Test + public void setStaticFieldViaInstance() throws Exception { + StaticFields staticFields = new StaticFields(); + setField(staticFields, null, "publicField", "xxx", null); + setField(staticFields, null, "privateField", "yyy", null); + + assertEquals("public static field", "xxx", StaticFields.publicField); + assertEquals("private static field", "yyy", StaticFields.getPrivateField()); + } + + @Test + public void getStaticFieldViaClass() throws Exception { + assertEquals("public static field", "public", getField(StaticFields.class, "publicField")); + assertEquals("private static field", "private", getField(StaticFields.class, "privateField")); + } + + @Test + public void getStaticFieldViaInstance() throws Exception { + StaticFields staticFields = new StaticFields(); + assertEquals("public static field", "public", getField(staticFields, "publicField")); + assertEquals("private static field", "private", getField(staticFields, "privateField")); + } + + @Test + public void invokeSetterMethodAndInvokeGetterMethodWithExplicitMethodNames() throws Exception { invokeSetterMethod(person, "setId", new Long(1), long.class); invokeSetterMethod(person, "setName", "Jerry", String.class); invokeSetterMethod(person, "setAge", new Integer(33), int.class); @@ -126,7 +238,7 @@ public void invokeSetterMethodWithExplicitSetterMethodNames() throws Exception { } @Test - public void invokeSetterMethodWithJavaBeanPropertyNames() throws Exception { + public void invokeSetterMethodAndInvokeGetterMethodWithJavaBeanPropertyNames() throws Exception { invokeSetterMethod(person, "id", new Long(99), long.class); invokeSetterMethod(person, "name", "Tom"); invokeSetterMethod(person, "age", new Integer(42)); @@ -217,23 +329,31 @@ public void invokeMethodSimulatingLifecycleEvents() { assertNull("text", component.getText()); } - @Test(expected = IllegalStateException.class) - public void invokeMethodWithIncompatibleArgumentTypes() { - invokeMethod(component, "subtract", "foo", 2.0); - } - - @Test(expected = IllegalStateException.class) + @Test public void invokeInitMethodBeforeAutowiring() { + exception.expect(IllegalStateException.class); + exception.expectMessage(equalTo("number must not be null")); invokeMethod(component, "init"); } - @Test(expected = IllegalStateException.class) + @Test + public void invokeMethodWithIncompatibleArgumentTypes() { + exception.expect(IllegalStateException.class); + exception.expectMessage(startsWith("Method not found")); + invokeMethod(component, "subtract", "foo", 2.0); + } + + @Test public void invokeMethodWithTooFewArguments() { + exception.expect(IllegalStateException.class); + exception.expectMessage(startsWith("Method not found")); invokeMethod(component, "configure", new Integer(42)); } - @Test(expected = IllegalStateException.class) + @Test public void invokeMethodWithTooManyArguments() { + exception.expect(IllegalStateException.class); + exception.expectMessage(startsWith("Method not found")); invokeMethod(component, "configure", new Integer(42), "enigma", "baz", "quux"); } diff --git a/spring-test/src/test/java/org/springframework/test/util/subpackage/StaticFields.java b/spring-test/src/test/java/org/springframework/test/util/subpackage/StaticFields.java new file mode 100644 index 000000000000..b100b433c817 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/util/subpackage/StaticFields.java @@ -0,0 +1,41 @@ +/* + * Copyright 2002-2015 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 + * + * http://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 fields; intended for use in unit tests. + * + * @author Sam Brannen + * @since 4.2 + */ +public class StaticFields { + + public static String publicField = "public"; + + private static String privateField = "private"; + + + public static void reset() { + publicField = "public"; + privateField = "private"; + } + + public static String getPrivateField() { + return privateField; + } + +} \ No newline at end of file