Support static fields in ReflectionTestUtils

Prior to this commit it was possible to set or get a static field using
ReflectionTestUtils but only if an instance of the target class was
available.

This commit introduces dedicated support for setting and getting static
fields in ReflectionTestUtils when only the target class is available.

Furthermore, this commit increases the robustness of
ReflectionTestUtilsTests regarding expected exceptions and simplifies
the Javadoc for ReflectionTestUtils.

Issue: SPR-6792
This commit is contained in:
Sam Brannen 2015-04-03 02:40:00 -04:00
parent 444aa4edcf
commit 063ef240c1
3 changed files with 334 additions and 52 deletions

View File

@ -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 @@ import org.springframework.util.StringUtils;
* methods.</li>
* </ul>
*
* <p>In addition, several methods in this class provide support for {@code static}
* fields &mdash; 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}.
*
* <p>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
* <em>accessible</em>, thus allowing one to set {@code protected},
* {@code private}, and <em>package-private</em> fields.
* <p>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 {@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} to the supplied {@code value}.
*
* <p>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}.
*
* <p>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}.
*
* <p>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 {@linkplain Field field} with the given {@code name}/{@code type}
* on the provided {@code targetObject}/{@code targetClass} to the supplied
* {@code value}.
*
* <p>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 <em>accessible</em>, thus allowing one to set {@code protected},
* {@code private}, and <em>package-private</em> 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");
// SPR-9571: inline Assert.notNull() in order to avoid accidentally invoking
// toString() on a non-null target.
if (targetClass == null) {
targetClass = targetObject.getClass();
}
Field field = ReflectionUtils.findField(targetClass, name, type);
// 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}.
*
* <p>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}.
*
* <p>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}.
*
* <p>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 <em>accessible</em>, thus allowing one to get {@code protected},
* {@code private}, and <em>package-private</em> 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);
}
/**

View File

@ -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 setFieldForStandardUseCases() throws Exception {
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 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 class ReflectionTestUtilsTests {
@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 class ReflectionTestUtilsTests {
}
@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 class ReflectionTestUtilsTests {
}
@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 class ReflectionTestUtilsTests {
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");
}

View File

@ -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;
}
}