![JAR search and dependency download from the Maven repository](/logo.png)
org.agileware.test.PropertiesTester Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of smartunit Show documentation
Show all versions of smartunit Show documentation
Unit test utilities library
The newest version!
package org.agileware.test;
/*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.junit.Assert;
/**
* This is a utility class to test bean properties accessor methods.
*
*/
public class PropertiesTester extends AbstractTester {
/**
* Property names exclusion list.
*/
protected List excludesByName = new ArrayList();
/**
* Property types exclusion list.
*/
protected List> excludesByType = new ArrayList>();
/**
* Property names mappings.
*/
protected Map> nameMappings = new HashMap>();
/**
* Sets a list of properties you want to exclude by name. Use this feature whenever you want to exclude some
* specific property from the testing. If you need to exclude multiple properties because because you can't provide
* an instance for the property type consider using setTypeExclusions
instead.
*
* @param names
* the list of property names you want to exclude.
* @return the PropertiesTester
instance.
*/
public PropertiesTester setNameExclusions(final String... names) {
excludesByName = Arrays.asList(names);
return this;
}
/**
* Sets the list of property types that are not going to be tested. Use this feature whenever your class uses some
* type you can't provide an instance for.
*
* @param types
* the list of types you want to be excluded from properties testing.
* @return the PropertiesTester
instance.
*/
public PropertiesTester setTypeExclusions(final Class>... types) {
excludesByType = Arrays.asList(types);
return this;
}
/**
* Sets the properties mapping by specifying field to property associations allowing to test properties which uses a
* field with a different name.
*
* Use this feature specifying a mapping of {"name", "authority"}
if you have something like:
*
*
* private String name;
*
* public void setAuthority(String name) {
* this.name = name;
* }
*
*
*
* If the mapping uses a field defined in a superclass then you should prefix the field with the hash (#) symbol,
* like {"#inheritedFieldName", "propertyName"}
, if you have something like:
*
*
* public void setPropertyName(String name) {
* super.inherithedFieldName = name;
* }
*
*
* @param mappings
* a list of string arrays where each array defines in it's first position the field name and in all
* subsequent positions the properties using that field. Multiple properties can be specified for each
* mapping and multiple mappings can be set.
* @return the PropertiesTester
instance.
*/
public PropertiesTester setNameMappings(final String[]... mappings) {
for (String[] mapping : mappings) {
Assert.assertTrue("A mapping is defined by an array of 2 strings minimum", mapping.length > 1);
Collection properties = nameMappings.get(mapping[0]);
if (properties == null) {
properties = new HashSet();
this.nameMappings.put(mapping[0], properties);
}
for (int i = 1; i < mapping.length; i++) {
properties.add(mapping[i]);
}
}
return this;
}
/**
* Tests all the properties defined in this class.
*
* A property is considered to have been defined locally if the class defines a field member of the same type used
* in the accessor methods. Accessor methods overriding superclass accessor methods or accessor methods for
* properties without an associated member field are not considered `defined properties`.
*
* @param type
* the class the test is going to be run on.
* @throws Exception
* thrown if any of the tests fail.
* @return the PropertiesTester
instance.
*/
public PropertiesTester testAll(Class> type) throws Exception {
this.testAll(type, true);
return this;
}
/**
* Test all the properties in this class using the definedOnly
parameter to restrict the set to locally
* defined properties or not. A property is considered to have been defined locally if the class defines a field
* member of the same type used in the accessor methods.
*
* @param type
* the class the test is going to be run on.
* @param definedOnly
* determines the set of properties to be tested: if you want to test accessor methods overriding
* superclass accessor methods or accessor methods using inherited fields you need to set this value to
* false
.
* @throws Exception
* thrown if any of the tests fail.
* @return the PropertiesTester
instance.
*/
public PropertiesTester testAll(Class> type, boolean definedOnly) throws Exception {
Object instance = this.getInstance(type);
for (Field field : type.getDeclaredFields()) {
this.testField(instance, type, field);
}
if (!definedOnly) {
for (Field field : this.listInherithed(type, type.getSuperclass(), new HashSet())) {
this.testField(instance, type, field);
}
}
return this;
}
/**
* Tests a property by name checking the accessor methods work as expected.
*
* @param type
* the class on which the test is going to be run.
* @param fieldName
* the name of the property to test.
* @param value
* the value used to check the accessor methods.
* @throws Exception
* thrown if any of the tests fail.
* @return the PropertiesTester
instance.
*/
public PropertiesTester test(Class> type, String fieldName, Object value) throws Exception {
Field field = type.getDeclaredField(fieldName);
Object instance = this.getInstance(type);
this.testField(instance, type, field);
return this;
}
/**
* Performs a field check including field name mappings.
*
* @param instance
* the instance on which the properties will be accessed.
* @param type
* the class under test.
* @param field
* the field to test.
* @throws Exception
* thrown if any of the tests fail.
*/
private void testField(Object instance, Class> type, Field field) throws Exception {
this.testField(instance, type, field, field.getName());
if (nameMappings.containsKey(field.getName())) {
for (String property : nameMappings.get(field.getName())) {
this.testField(instance, type, field, property);
}
}
if (nameMappings.containsKey("#" + field.getName())) {
for (String property : nameMappings.get("#" + field.getName())) {
this.testField(instance, type, field, property);
}
}
}
/**
* Performs a property check.
*
* @param instance
* the instance on which the properties will be accessed.
* @param type
* the class under test.
* @param field
* the field to test.
* @param property
* the property to test.
* @throws Exception
* thrown if any of the tests fail.
*/
private void testField(Object instance, Class> type, Field field, final String property) throws Exception {
Method setter = this.getSetter(type, property, field.getType(), false);
Method getter = this.getGetter(type, property, field.getType(), false);
if (setter != null && getter != null) {
Object value = this.getInstance(field.getType());
setter.invoke(instance, value);
Assert.assertEquals("Field test failed on `" + field.getName() + "`", value, getter.invoke(instance));
if (!field.getType().isPrimitive()) {
setter.invoke(instance, field.getType().cast(null));
Assert.assertEquals("Null field test failed on `" + field.getName() + "`", null, getter.invoke(instance));
}
} else {
if (setter != null) {
Object value = this.getInstance(field.getType());
field.setAccessible(true);
setter.invoke(instance, value);
Assert.assertEquals("Field test failed on `" + field.getName() + "`", value, field.get(instance));
if (!field.getType().isPrimitive()) {
setter.invoke(instance, field.getType().cast(null));
Assert.assertEquals("Null field test failed on `" + field.getName() + "`", null, field.get(instance));
}
}
if (getter != null) {
Object value = this.getInstance(field.getType());
field.setAccessible(true);
field.set(instance, value);
Assert.assertEquals("Field test failed on `" + field.getName() + "`", value, getter.invoke(instance));
if (!field.getType().isPrimitive()) {
field.set(instance, null);
Assert.assertEquals("Null field test failed on `" + field.getName() + "`", null, getter.invoke(instance));
}
}
}
}
/**
* Recursively traverse the class hierarchy to find fields used by access methods defined on the class under test.
*
* @param orig
* the class under test.
* @param type
* the class in the hierarchy currently under field investigation.
* @param fields
* the collection of fields found.
* @return a collection of fields found in the hierarchy mapping to accessor methods locally defined.
*/
private Collection listInherithed(Class> orig, Class> type, Collection fields) {
if (type == null || type.equals(Object.class)) {
return fields;
} else {
for (Field field : type.getDeclaredFields()) {
if (this.getGetter(orig, field.getName(), field.getType(), true) != null
|| this.getSetter(orig, field.getName(), field.getType(), true) != null) {
field.setAccessible(true);
fields.add(field);
}
if (nameMappings.containsKey("#" + field.getName())) {
for (String property : nameMappings.get("#" + field.getName())) {
if (this.getGetter(orig, property, field.getType(), true) != null
|| this.getSetter(orig, property, field.getType(), true) != null) {
field.setAccessible(true);
fields.add(field);
}
}
}
}
return listInherithed(orig, type.getSuperclass(), fields);
}
}
/**
* Returns the setter method for a property.
*
* @param type
* the class under test.
* @param propertyName
* the property name.
* @param propertyType
* the property type.
* @return the setter method.
*/
private Method getSetter(final Class> type, final String propertyName, final Class> propertyType,
boolean declared) {
if (excludesByName.contains(propertyName) || excludesByType.contains(propertyType)) {
return null;
}
try {
if (declared) {
return type.getDeclaredMethod(this.getMethodName("set", propertyName), propertyType);
} else {
return type.getMethod(this.getMethodName("set", propertyName), propertyType);
}
} catch (NoSuchMethodException e) {
return null;
}
}
/**
* Returns the getter method for a property.
*
* @param type
* the class under test.
* @param propertyName
* the property name.
* @param propertyType
* the property type.
* @return the getter method.
*/
private Method getGetter(final Class> type, final String propertyName, final Class> propertyType,
boolean declared) {
if (excludesByName.contains(propertyName) || excludesByType.contains(propertyType)) {
return null;
}
Method method = null;
try {
if (declared) {
method = type.getDeclaredMethod(this.getMethodName("get", propertyName));
} else {
method = type.getMethod(this.getMethodName("get", propertyName));
}
} catch (NoSuchMethodException nsme) {
if (propertyType.equals(Boolean.class) || propertyType.equals(boolean.class)) {
try {
if (declared) {
method = type.getDeclaredMethod(this.getMethodName("is", propertyName));
} else {
method = type.getMethod(this.getMethodName("is", propertyName));
}
} catch (NoSuchMethodException nsmeAgain) {
return null;
}
}
}
if (method != null && !method.getReturnType().equals(propertyType)) {
method = null;
}
return method;
}
/**
* Returns the proper string concatenation for an accessor method.
*
* @param prefix
* the method prefix.
* @param property
* the property name.
* @return the camel case concatenation of prefix and property.
*/
private final String getMethodName(final String prefix, final String property) {
return prefix + Character.toUpperCase(property.charAt(0)) + property.substring(1);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy