package com.thinkbiganalytics.scheduler.support; /*- * #%L * thinkbig-scheduler-core * %% * Copyright (C) 2017 ThinkBig Analytics * %% * 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. * #L% */ import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Date; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** * This helper class can be used to unit test the get/set methods of JavaBean-style Value Objects. */ public class JavaBeanTester { private static Date today = new Date(); /** * Tests the get/set methods of the specified class. * * @param <T> the type parameter associated with the class under test * @param clazz the Class under test * @param skipThese the names of any properties that should not be tested * @throws IntrospectionException thrown if the Introspector.getBeanInfo() method throws this exception for the class under test */ public static <T> void test(final Class<T> clazz, final String... skipThese) throws IntrospectionException { final PropertyDescriptor[] props = Introspector.getBeanInfo(clazz).getPropertyDescriptors(); nextProp: for (PropertyDescriptor prop : props) { // Check the list of properties that we don't want to test for (String skipThis : skipThese) { if (skipThis.equals(prop.getName())) { continue nextProp; } } findBooleanIsMethods(clazz, prop); final Method getter = prop.getReadMethod(); final Method setter = prop.getWriteMethod(); if (getter != null && setter != null) { // We have both a get and set method for this property final Class<?> returnType = getter.getReturnType(); final Class<?>[] params = setter.getParameterTypes(); if (params.length == 1 && params[0] == returnType) { // The set method has 1 argument, which is of the same type as the return type of the get method, so we can test this property try { // Build a value of the correct type to be passed to the set method Object value = buildValue(prop, returnType); // Build an instance of the bean that we are testing (each property test gets a new instance) T bean = clazz.newInstance(); // Call the set method, then check the same value comes back out of the get method setter.invoke(bean, value); final Object expectedValue = value; final Object actualValue = getter.invoke(bean); assertEquals(String.format("Failed while testing property %s", prop.getName()), expectedValue, actualValue); } catch (Exception ex) { fail(String.format("An exception was thrown while testing the property %s: %s", prop.getName(), ex.toString())); } } } } } private static Object buildMockValue(Class<?> clazz) { if (!Modifier.isFinal(clazz.getModifiers())) { // Insert a call to your favourite mocking framework here return null; } else { return null; } } private static Object buildValue(PropertyDescriptor prop, Class<?> clazz) throws InstantiationException, IllegalAccessException, IllegalArgumentException, SecurityException, InvocationTargetException { // If we are using a Mocking framework try that first... final Object mockedObject = buildMockValue(clazz); if (mockedObject != null) { return mockedObject; } // Next check for a no-arg constructor final Constructor<?>[] ctrs = clazz.getConstructors(); for (Constructor<?> ctr : ctrs) { if (ctr.getParameterTypes().length == 0) { // The class has a no-arg constructor, so just call it return ctr.newInstance(); } } // Specific rules for common classes if (clazz == String.class) { return "testvalue"; } else if (clazz.isArray()) { return Array.newInstance(clazz.getComponentType(), 1); } else if (clazz == boolean.class || clazz == Boolean.class) { return true; } else if (clazz == int.class || clazz == Integer.class) { return 1; } else if (clazz == long.class || clazz == Long.class) { return 1L; } else if (clazz == double.class || clazz == Double.class) { return 1.0D; } else if (clazz == float.class || clazz == Float.class) { return 1.0F; } else if (clazz == char.class || clazz == Character.class) { return 'Y'; } else if (clazz == Date.class) { return today; } else if (clazz.isEnum()) { return clazz.getEnumConstants()[0]; // Add your own rules here } else { fail("Unable to build an instance of class " + clazz.getName() + " prop [" + prop.getDisplayName() + "], please add some code to the " + JavaBeanTester.class.getName() + " class to do this."); return null; // for the compiler } } /** * Hunt down missing Boolean read method if needed as Introspector cannot find 'is' getters * for Boolean type properties. * * @param clazz the type being introspected * @param descriptor the property descriptor found so far */ public static <T> void findBooleanIsMethods(Class<T> clazz, PropertyDescriptor descriptor) throws IntrospectionException { if (needToFindReadMethod(descriptor)) { findTheReadMethod(descriptor, clazz); } } private static boolean needToFindReadMethod(PropertyDescriptor property) { return property.getReadMethod() == null && property.getPropertyType() == Boolean.class; } private static <T> void findTheReadMethod(PropertyDescriptor descriptor, Class<T> clazz) { try { PropertyDescriptor pd = new PropertyDescriptor(descriptor.getName(), clazz); descriptor.setReadMethod(pd.getReadMethod()); } catch (IntrospectionException e) { } } }