View Javadoc

1   /**
2    * Copyright (C) 2011 Franck Valentin
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.outsideMyBox.testUtils;
17  
18  import java.lang.reflect.Array;
19  import java.lang.reflect.Constructor;
20  import java.lang.reflect.Method;
21  import java.text.MessageFormat;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Map.Entry;
30  import java.util.Set;
31  
32  /**
33   * This class provides methods to easily and quickly test and improve the test coverage of 'bean like'
34   * objects (i.e. objects with setters and getters but that not necessarily implement the Serializable 
35   * interface and can have non default constructors).
36   * The beanLike objects:
37   *    <ul>
38   *    <li>have a default constructor and/or constructors that only initialise properties,</li>
39   *    <li>have optional setters (setXXX()),</li>
40   *    <li>have accessors (isXXX(), getXXX()) for all the properties,</li>
41   *    <li>properties are either readonly or set by a either a constructor or a setter (or both).</li>
42   *    </ul>
43   * <p>
44   * All the initial values, setters, getters, non default constructors, hashCode(), equals(), toString()
45   * can be automatic tested from a map of default and non default property/value.
46   */
47  public final class BeanLikeTester {
48  	
49  	// ------------------------------------  Class variables  ----------------------------------------
50  
51  	/** Convenient class to define a list of property names with their corresponding value. */
52  	public static final class PropertiesAndValues extends HashMap<String, Object> {
53  		private static final long serialVersionUID = 1L;
54  
55  		public PropertiesAndValues(PropertiesAndValues defaultValues) {
56  			super(defaultValues);
57  		}
58  
59  		public PropertiesAndValues() {
60  			super();
61  		}
62  	}
63  
64  	/** 
65  	 * Convenient class to define a list of constructor signatures with their corresponding property name.
66  	 * @param List<Class<?> List of the constructor's parameter types.
67  	 * @param List<String> List of corresponding property names.
68  	 * */
69  	public static final class ConstructorSignatureAndPropertiesMapping extends HashMap<List<Class<?>>, List<String>> {
70  		private static final long serialVersionUID = 1L;
71  	}
72  
73  	private static final ConstructorSignatureAndPropertiesMapping NOARG_SIGNATUREANDPROPS = new ConstructorSignatureAndPropertiesMapping();
74  
75  	static {
76  		NOARG_SIGNATUREANDPROPS.put(Collections.<Class<?>> emptyList(), Collections.<String> emptyList());
77  	}
78  
79  	private static final class AClassToBeTestedAgainst {
80  		private static AClassToBeTestedAgainst instance = new AClassToBeTestedAgainst();
81  
82  		private AClassToBeTestedAgainst() {
83  		}
84  	}
85  
86  	// -----------------------------------  Instance variables  ------------------------------------
87  
88  	private final Class<?>                                 beanLikeClass;
89  	private final ConstructorSignatureAndPropertiesMapping constructorsSignaturesAndProperties;
90  	private final Set<String>                              gettablePropertyNames;
91  	private final Set<String>                              settablePropertyNames;
92  	private final Set<String>                              mutablePropertyNames;
93  	private final Map<String, Method>                      accessors;
94  	private final Map<String, Method>                      setters;
95  
96  	// ------------------------------------  Constructors  -------------------------------------------
97  	/**
98  	 * Create a BeanLikeTester with a specific beanLike to test.
99  	 * @param beanLikeClass The 'beanLike' to test.
100 	 * @param constructorsSignaturesAndProperties The signature of all the possible constructors
101 	 *        (The parameters of the constructors must only set properties).<br/>
102 	 *        key: constructor's signature. value: corresponding property name.<br/>
103 	 *        For beans the map can be empty or null.
104 	 * @throws BeanLikeTesterException if at least one of the signatures doesn't correspond to a constructor or
105 	 *         the constructors doesn't only define properties or setters or accessors are invalid.
106 	 */
107 	public BeanLikeTester(Class<?> beanLikeClass, ConstructorSignatureAndPropertiesMapping constructorsSignaturesAndProperties) {
108 		this.beanLikeClass = beanLikeClass;
109 		this.constructorsSignaturesAndProperties = constructorsSignaturesAndProperties == null ? NOARG_SIGNATUREANDPROPS : constructorsSignaturesAndProperties;
110 		accessors = getAccessors();
111 		setters = getSetters();
112 		gettablePropertyNames = accessors.keySet();
113 		settablePropertyNames = setters.keySet();
114 		mutablePropertyNames = getMutableProperyNames();
115 		verifyConstructorSignaturesMatchSignaturesFromArgs();
116 		verifySettersAndAccessorsAreValid();
117 		verifyAllPropertiesHaveAnAccessor();
118 	}
119 
120 	/**
121 	 * Create a BeanLikeTester with a specific bean to test.
122 	 * @param beanClass The bean class to test.
123 	 * @throws BeanLikeTesterException See {@link #BeanLikeTester(Class, ConstructorSignatureAndPropertiesMapping) }
124 	 */
125 	public BeanLikeTester(Class<?> beanClass) {
126 		this(beanClass, null);
127 	}
128 
129 	// ------------------------------------  Private methods  ----------------------------------------
130 
131 	/**
132 	 * Get the properties that can be set by either a constructor or a setter.
133 	 * @return Set of mutable properties.
134 	 */
135 	private Set<String> getMutableProperyNames() {
136 		final Set<String> settableProperties = new HashSet<String>();
137 		for (final List<String> props : constructorsSignaturesAndProperties.values()) {
138 			settableProperties.addAll(props);
139 		}
140 		settableProperties.addAll(settablePropertyNames);
141 		return settableProperties;
142 	}
143 
144 	/**
145 	 * Verify that the beanLike constructors' signatures to test are the same as the ones defined by the beanLike class.
146 	  * @throws BeanLikeTesterException if at least one of the signatures doesn't correspond to a constructor.
147 	 */
148 	private void verifyConstructorSignaturesMatchSignaturesFromArgs() {
149 		final Set<List<Class<?>>> signaturesFromArgs = constructorsSignaturesAndProperties.keySet();
150 		final Set<List<Class<?>>> signaturesFromConstructor = new HashSet<List<Class<?>>>();
151 
152 		for (final Constructor<?> constructor : beanLikeClass.getConstructors()) {
153 			final List<Class<?>> signature = Arrays.asList(constructor.getParameterTypes());
154 			signaturesFromConstructor.add(signature);
155 
156 		}
157 		if (!signaturesFromArgs.equals(signaturesFromConstructor)) {
158 			throw new BeanLikeTesterException("The signatures from the constructor's argument must be the same as the bean:\nFrom args:  " + signaturesFromArgs
159 			                                  + "\nFrom object:" + signaturesFromConstructor);
160 		}
161 	}
162 
163 	/**
164 	 * Test that all the setters return void, the 'is' accessors return a boolean and the other getters an object.
165 	 * @throws BeanLikeTesterException if one of the previous condition is not met.
166 	 */
167 	private void verifySettersAndAccessorsAreValid() {
168 		final Method[] methods = beanLikeClass.getMethods();
169 
170 		for (final Method method : methods) {
171 			final String methodName = method.getName();
172 			if (isSetter(method)) {
173 					throw new BeanLikeTesterException("The method '" + methodName + "' must not return an object.");
174 			}
175 			if (isIsGetter(method)) {
176 					throw new BeanLikeTesterException("The method '" + methodName + "' doesn't return a boolean");
177 			}
178 			else if (isGetGetter(method)) {
179 					throw new BeanLikeTesterException("The method '" + methodName + "' doesn't return an object");
180 			}
181 		}
182 	}
183 
184 	private boolean isSetter(Method method) {
185 	   return method.getName().startsWith("set") && !method.getReturnType().equals(Void.TYPE);
186    }
187 
188 	private boolean isIsGetter(Method method) {
189 	   return method.getName().startsWith("is") && !method.getReturnType().equals(Boolean.class) && !method.getReturnType().equals(boolean.class);
190    }
191 	
192 	private boolean isGetGetter(Method method) {
193 	   return method.getName().startsWith("get") && !method.getName().equals("getClass") && (method.getParameterTypes().length == 0) && method.getReturnType().equals(Void.TYPE);
194    }
195 	
196 	/**
197 	 * Verify that all properties have an accessor
198 	  * @throws BeanLikeTesterException if the verification fails.
199 	 */
200 	private void verifyAllPropertiesHaveAnAccessor() {
201 		final Set<String> nonAccessibleProperties = new HashSet<String>(mutablePropertyNames);
202 		nonAccessibleProperties.removeAll(gettablePropertyNames);
203 		if (!nonAccessibleProperties.isEmpty()) {
204 			throw new BeanLikeTesterException("The following properties don't have any accessor:" + nonAccessibleProperties);
205 		}
206 	}
207 
208 	/**
209 	 * Create a new instance of a class using a constructor with parameters.
210 	 * @param fullyQualifiedClassName Fully qualified class name of the instance to create.
211 	 * @param constructorSignature Signature of the constructor to use.
212 	 * @param constructorParams Constructor's parameters.
213 	 * @return New instance.
214 	 * @throws BeanLikeTesterException if the instance couldn't be created.
215 	 */
216 	private static Object createNewInstance(String fullyQualifiedClassName, Class<?>[] constructorSignature, Object... constructorParams) {
217 		try {
218 			final Class<?> classToInstantiate = Class.forName(fullyQualifiedClassName);
219 			final Constructor<? extends Object> classConstructor = classToInstantiate.getConstructor(constructorSignature);
220 			return classConstructor.newInstance(constructorParams);
221 		} catch (final Exception e) {
222 			final String msg = MessageFormat.format("exception msg: {0} \n\tfullyQualifiedClassName: {1} \n\tconstructorSignature: {2} \n\tconstructorParams: {3}",
223 			                                        e,
224 			                                        fullyQualifiedClassName,
225 			                                        Arrays.asList(constructorSignature),
226 			                                        Arrays.asList(constructorParams));
227 			throw new BeanLikeTesterException(msg, e);
228 		}
229 	}
230 
231 	/**
232 	 * Invoke an object's method.
233 	 * @param object Object.
234 	 * @param method Method to invoke.
235 	 * @param args Method's arguments.
236 	 * @return Object returned by the method invocation.
237 	 * @throws BeanLikeTesterException if an exception occurred.
238 	 */
239 	private static Object invokeMethod(Object object, Method method, Object... args) {
240 		try {
241 			return method.invoke(object, args);
242 		} catch (final Exception e) {
243 			throw new BeanLikeTesterException(e.getMessage(), e);
244 		}
245 	}
246 
247 	private static String getPropertyNameFromMethodName(String methodName) {
248 		final String propertyName = methodName.replaceFirst("^is|set|get", "");
249 		return Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1);
250 	}
251 
252 	private static Object[] createArrayFromArrayObject(Object o) {
253 		if(!o.getClass().getComponentType().isPrimitive())
254 			return (Object[])o;
255 
256 		int arrayLength = Array.getLength(o);
257 		Object elements[] = new Object[arrayLength];
258 		for(int i = 0; i < arrayLength; i++){
259 			elements[i] = Array.get(o, i);
260 		}
261 		return elements;
262 	}
263 
264 	private static boolean areValuesDifferent(Object value1, Object value2) {
265 		if ((value1 !=null) && (value2 != null) && (value1.getClass().isArray()) && (value2.getClass().isArray())) {
266 			final Object[] array1 = createArrayFromArrayObject(value1);
267 			final Object[] array2 = createArrayFromArrayObject(value2);
268 			return !Arrays.deepEquals(array1, array2);
269 		}
270 		final boolean conditionToFail1 = (value1 != null) && ((value2 == null) || !value1.equals(value2));
271 		final boolean conditionToFail2 = (value2 != null) && ((value1 == null) || !value2.equals(value1));
272 		return conditionToFail1 || conditionToFail2;
273 	}
274 
275 	/**
276 	 * Set a property via its setter.
277 	 * @param beanLike beanLike to set the property to.
278 	 * @param propertyName Property name.
279 	 * @param value Property's value.
280 	 * @throws BeanLikeTesterException if the property couldn't be set.
281 	 */
282 	private void setProperty(Object beanLike, String propertyName, Object value) {
283 		final Method setter = setters.get(propertyName);
284 		invokeMethod(beanLike, setter, new Object[] { value });
285 	}
286 
287 	/**
288 	 * Get all the accessors.
289 	 * @return List of accessors.
290 	 */
291 	private Map<String, Method> getAccessors() {
292 		final Map<String, Method> propsAndAccessors = new HashMap<String, Method>();
293 		final Method[] methods = beanLikeClass.getMethods();
294 
295 		for (final Method method : methods) {
296 			final String methodName = method.getName();
297 			if (methodName.startsWith("is")) {
298 				propsAndAccessors.put(getPropertyNameFromMethodName(methodName), method);
299 			}
300 			else if (methodName.startsWith("get") && !methodName.equals("getClass") && (method.getParameterTypes().length == 0)) {
301 				propsAndAccessors.put(getPropertyNameFromMethodName(methodName), method);
302 			}
303 		}
304 		return propsAndAccessors;
305 	}
306 
307 	/**
308 	 * Get a new beanLike instance using a specific constructor and properties values.
309 	 * @param constructor Constructor used to create the new instance.
310 	 * @param propertiesAndValues List of property names and their corresponding values.<br/>
311 	 *                            The properties unused by the constructor are ignored.
312 	 * @return New beanLike instance.
313 	 */
314 	private Object getNewInstance(Constructor<?> constructor, PropertiesAndValues propertiesAndValues) {
315 		final Class<?>[] constructorSignature = constructor.getParameterTypes();
316 
317 		final List<String> propertyNames = constructorsSignaturesAndProperties.get(Arrays.asList(constructorSignature));
318 		final List<Object> propertyValues = new ArrayList<Object>();
319 
320 		if (propertyNames != null) {
321 			for (final String propertyName : propertyNames) {
322 				propertyValues.add(propertiesAndValues.get(propertyName));
323 			}
324 		}
325 		return createNewInstance(beanLikeClass.getName(), constructorSignature, propertyValues.toArray());
326 	}
327 
328 	/**
329 	 * Return all the setters.
330 	 * @return List of setters
331 	 */
332 	private Map<String, Method> getSetters() {
333 		final Map<String, Method> propsAndSetters = new HashMap<String, Method>();
334 		final Method[] methods = beanLikeClass.getMethods();
335 
336 		for (final Method method : methods) {
337 			final String methodName = method.getName();
338 			if (methodName.startsWith("set")) {
339 				propsAndSetters.put(getPropertyNameFromMethodName(methodName), method);
340 			}
341 		}
342 		return propsAndSetters;
343 	}
344 
345 	/**
346 	 * Verify that the beanLike object returns the expected value for the properties 'propertiesToVerify'.
347 	 * @param beanLike Object to test.
348 	 * @param propertiesToVerify List of properties to verify.
349 	 * @param propertiesAndValuesExpected Property names and their corresponding value expected.
350 	 * @throws BeanLikeTesterException if the object doesn't return an expected value.
351 	 */
352 	private void verifyPropertyValuesFromAccessors(Object beanLike, List<String> propertiesToVerify, final PropertiesAndValues propertiesAndValuesExpected) {
353 		for (final String propertyToVerify : propertiesToVerify) {
354 			verifyPropertyValueFromAccessor(beanLike, propertyToVerify, propertiesAndValuesExpected.get(propertyToVerify));
355 		}
356 	}
357 
358 	/**
359 	 * Verify that the beanLike object returns the expected value for the property 'propertyName'.
360 	 * @param beanLike Instance to test.
361 	 * @param propertyName Property to verify.
362 	 * @param expectedValue Expected value.
363 	 * @throws BeanLikeTesterException if the object doesn't return the expected value.
364 	 */
365 	private void verifyPropertyValueFromAccessor(Object beanLike, String propertyName, Object expectedValue) {
366 		final Object returnedValue = getProperty(beanLike, propertyName);
367 		if (areValuesDifferent(expectedValue, returnedValue)) {
368 			throw new BeanLikeTesterException("The value of the property '" + propertyName + "' returned (" + returnedValue
369 			                                  + ") is not the same as the one expected (" + expectedValue + ")");
370 		}
371 	}
372 
373 	private Object getProperty(Object beanLike, String propertyName) {
374 		final Method accessor = accessors.get(propertyName);
375 		final Object returnedValue = invokeMethod(beanLike, accessor, (Object[]) null);
376 		return returnedValue;
377 	}
378 
379 	/**
380 	 * Verify that the property names to test are the same as the beanLike properties.
381 	 * @param propertyNamesToTest Property names to test.
382 	 * @throws BeanLikeTesterException if the set of properties to test is different from the properties accessible from the object.
383 	 */
384 	private void verifyPropertyNamesAreTheSameAs(Set<String> propertyNamesToTest) {
385 		if (!gettablePropertyNames.equals(propertyNamesToTest)) {
386 			throw new BeanLikeTesterException("The set of properties to test is different from the properties accessible from the object:\nFrom object: "
387 			                                  + gettablePropertyNames + "\nTo test:     " + propertyNamesToTest);
388 		}
389 	}
390 
391 	private void verifyContainsAtLeastAllMutableProperties(Set<String> properties) {
392 		if (!properties.containsAll(mutablePropertyNames)) {
393 			throw new BeanLikeTesterException("The properties defined in parameter must at least contain all the settable properties of the object.\nParameter:"
394 			                                  + properties + "\nObject:" + mutablePropertyNames);
395 		}
396 	}
397 
398 	private Object createObjectWithSecificPropertySet(PropertiesAndValues propsWithDefaultValue, String property, Object value) {
399 		final PropertiesAndValues propsWithValue = new PropertiesAndValues(propsWithDefaultValue);
400 		propsWithValue.put(property, value);
401 
402 		// If possible create an object with default values and then set the property.
403 		if (settablePropertyNames.contains(property)) {
404 			final Object object = createObjectWithDefaultValues(propsWithDefaultValue);
405 			setProperty(object, property, value);
406 			return object;
407 		}
408 
409 		// If no setters existed then find the first combination constructor that could set the property.
410 		for (final Entry<List<Class<?>>, List<String>> entry : constructorsSignaturesAndProperties.entrySet()) {
411 			final List<String> constructorPropertyNames = entry.getValue();
412 			if (constructorPropertyNames.contains(property)) {
413 				final Class<?>[] constructorSignature = entry.getKey().toArray(new Class[0]);
414 				final List<Object> constructorValues = new ArrayList<Object>();
415 				for (final String propertyName : constructorPropertyNames) {
416 					constructorValues.add(propsWithValue.get(propertyName));
417 				}
418 				return createNewInstance(beanLikeClass.getName(), constructorSignature, constructorValues.toArray());
419 			}
420 		}
421 		throw new RuntimeException("The property '" + property + "' must be settable by either a setter or a constructor!");
422 	}
423 
424 	private Object createObjectWithDefaultValues(PropertiesAndValues propsWithDefaultValue) {
425 		// Create the object with default values (any constructor will do).
426 		final Constructor<?> constructor = beanLikeClass.getConstructors()[0];
427 		return getNewInstance(constructor, propsWithDefaultValue);
428 	}
429 
430 	private void verifyAllValuesFromMutablePropsAreDifferent(PropertiesAndValues propsWithValue, PropertiesAndValues propsWithOtherValue) {
431 		for (final Entry<String, Object> entry : propsWithValue.entrySet()) {
432 			final String property = entry.getKey();
433 			final Object value = entry.getValue();
434 			if (mutablePropertyNames.contains(property) && !areValuesDifferent(value, propsWithOtherValue.get(property))) {
435 				throw new BeanLikeTesterException("The value of the  property '" + property + "' must be different in the parameters.");
436 			}
437 		}
438 	}
439 
440 	// ------------------------------------  Public methods  -----------------------------------------
441 
442 	/**
443 	 * Test that the default value of the properties (returned by the accessors) 
444 	 * are the same as the one defined by the parameter.<br/>
445 	 * The properties are also tested for objects created with all the possible constructors.
446 	 * 
447 	 * @param expectedDefaultValues Property names and their expected default value. 
448 	 * @throws BeanLikeTesterException if the test fails.<br/>
449 	 *                        i.e. if one of the values returned by one of the accessors is different from the expected default value 
450 	 *                        or the properties defined by 'expectedDefaultValues' don't correspond to the beanLike properties.
451 	 */
452 	public void testDefaultValues(PropertiesAndValues expectedDefaultValues) {
453 
454 		verifyPropertyNamesAreTheSameAs(expectedDefaultValues.keySet());
455 		// Test the initial value for all the possible ways to create the object. 
456 		for (final Constructor<?> constructor : beanLikeClass.getConstructors()) {
457 			final Object beanLike = getNewInstance(constructor, expectedDefaultValues);
458 
459 			for (final Entry<String, Method> entry : accessors.entrySet()) {
460 				final String propertyName = entry.getKey();
461 				final Object expected = expectedDefaultValues.get(propertyName);
462 				final Object returned = getProperty(beanLike, propertyName);
463 				if (areValuesDifferent(returned, expected)) {
464 					throw new BeanLikeTesterException("The value of the property '" + propertyName + "' returned (" + returned
465 					                                  + ") is not the same as the one expected (" + expected + ")");
466 				}
467 			}
468 		}
469 	}
470 
471 	/**
472 	 * Test that all the mutators (setters, and constructors with arguments) can change their property
473 	 * and that the accessors reflect the change.
474 	 * 
475 	 * @param propsWithValue Property names (keys) and their value.<br/>
476 	 *                       It must at least contain all the settable properties.
477 	 * @param otherPropsWithValue Property names (keys) and their value different from 'propsWithValue'.</br>
478 	 *                            It must at least contain all the settable properties.
479 	 * @throws BeanLikeTesterException if the test fails.
480 	 */
481 	public void testMutatorsAndAccessors(PropertiesAndValues propsWithValue, PropertiesAndValues otherPropsWithValue) {
482 		verifyContainsAtLeastAllMutableProperties(propsWithValue.keySet());
483 		verifyContainsAtLeastAllMutableProperties(otherPropsWithValue.keySet());
484 
485 		// --- Test the modification from all the constructors.
486 		for (final Constructor<?> constructor : beanLikeClass.getConstructors()) {
487 			final List<Class<?>> constructorSignature = Arrays.asList(constructor.getParameterTypes());
488 
489 			final Object beanLike = getNewInstance(constructor, otherPropsWithValue);
490 			// If it's not a constructor by default, test that the properties defined by the constructor's arguments are effectively set.
491 			if (!constructorSignature.isEmpty()) {
492 				final List<String> propertyNamesInConstructor = constructorsSignaturesAndProperties.get(constructorSignature);
493 				verifyPropertyValuesFromAccessors(beanLike, propertyNamesInConstructor, otherPropsWithValue);
494 			}
495 
496 			// --- Test the modifications from the setters.
497 			// As a non default value may have already been set by the constructor, we first set the default value, check it and
498 			// then set another value and check it again.
499 			final List<PropertiesAndValues> rounds = new ArrayList<PropertiesAndValues>();
500 			rounds.add(propsWithValue);
501 			rounds.add(otherPropsWithValue);
502 			// For default values and then other values:
503 			for (final PropertiesAndValues propertiesAndValues : rounds) {
504 				// For all the setters:
505 				for (final Entry<String, Method> entry : setters.entrySet()) {
506 					final String propertyName = entry.getKey();
507 					final Object valueToSet = propertiesAndValues.get(propertyName);
508 					setProperty(beanLike, propertyName, valueToSet);
509 					verifyPropertyValueFromAccessor(beanLike, propertyName, valueToSet);
510 				}
511 			}
512 		}
513 	}
514 
515 	/**
516 	 * Test that equals() and hashCode() take into account all the properties and return the correct values.<br/>
517 	 * 
518 	 * @param propsWithDefaultValue Property names (keys) and their value.<br/>
519 	 *                              It must at least contain all the settable properties.
520 	 * @param propsWithOtherValue Property names (keys) and their value different from 'propsWithValue'.</br>
521 	 *                            It must at least contain all the settable properties.
522 	 * @throws BeanLikeTesterException if the test fails.
523 	 */
524 	public void testEqualsAndHash(PropertiesAndValues propsWithDefaultValue, PropertiesAndValues propsWithOtherValue) {
525 		verifyContainsAtLeastAllMutableProperties(propsWithDefaultValue.keySet());
526 		verifyContainsAtLeastAllMutableProperties(propsWithOtherValue.keySet());
527 		verifyAllValuesFromMutablePropsAreDifferent(propsWithDefaultValue, propsWithOtherValue);
528 
529 		final Object defaultObj = createObjectWithDefaultValues(propsWithDefaultValue);
530 		final int defaultHashCode = defaultObj.hashCode();
531 
532 		// Verify that the bean is equal to itself.
533 		if (!defaultObj.equals(defaultObj)) {
534 			throw new BeanLikeTesterException("The equals method must return true when the object is compared to itself:\nObject:" + defaultObj);
535 		}
536 
537 		if (defaultObj.equals(null)) {
538 			throw new BeanLikeTesterException("The comparison with null must return false.\nObject:" + defaultObj);
539 		}
540 
541 		if (defaultObj.equals(AClassToBeTestedAgainst.instance)) {
542 			throw new BeanLikeTesterException("The comparison with another class must return false.\nObject:" + defaultObj);
543 		}
544 
545 		for (final Entry<String, Object> entry : propsWithOtherValue.entrySet()) {
546 			final String property = entry.getKey();
547 			final Object value = entry.getValue();
548 			// Test only the mutable properties.
549 			if (mutablePropertyNames.contains(property)) {
550 				final Object otherObject1 = createObjectWithSecificPropertySet(propsWithDefaultValue, property, value);
551 				final int otherHashCode1 = otherObject1.hashCode();
552 
553 				if (!areValuesDifferent(defaultHashCode, otherHashCode1)) {
554 					throw new BeanLikeTesterException("The hashcodes of different objects should be different for the tests, please change the values or check that hashcode() is correct\nobject1:"
555 					                                  + defaultObj + " hashcode:" + defaultHashCode + "\nobject2:" + otherObject1 + " hashcode:" + otherHashCode1);
556 				}
557 
558 				// Verify that the bean is not equal to the default one as one property has been changed.
559 				final boolean equals1 = otherObject1.equals(defaultObj);
560 				final boolean equals2 = defaultObj.equals(otherObject1);
561 				if (equals1 || equals2) {
562 					throw new BeanLikeTesterException("The equals method must return false for the comparison between objects with different properties:\nobject1:"
563 					                                  + otherObject1 + "\nobject2:" + defaultObj);
564 				}
565 
566 				// Create another object exactly the same properties to test equals() and hashCode()
567 				final Object otherObject2 = createObjectWithSecificPropertySet(propsWithDefaultValue, property, value);
568 				final int otherHashCode2 = otherObject2.hashCode();
569 				// Two beans with the same properties must be equal.
570 				if (!otherObject1.equals(otherObject2)) {
571 					throw new BeanLikeTesterException("The equals method should return true for the comparison between objects with the same properties:\nobject1:"
572 					                                  + otherObject1 + "\nobject2:" + otherObject2);
573 				}
574 
575 				// Two beans with the same properties must have the same hashcode.
576 				if (!(otherHashCode1 == otherHashCode2)) {
577 					throw new BeanLikeTesterException("The hashcodes must be equal:\nobject1:" + otherObject1 + " hashcode:" + otherHashCode1 + "\nobject2:"
578 					                                  + otherObject2 + " hashcode:" + otherHashCode2);
579 				}
580 
581 			}
582 		}
583 	}
584 
585 	/**
586 	 * Test that the method toString() returns a different String if one of the settable properties has changed.
587 	 * 
588 	 * @param propsWithDefaultValue Property names (keys) and their value.<br/>
589 	 *                              It must at least contain all the settable properties.
590 	 * @param propsWithOtherValue Property names (keys) and their value different from 'propsWithDefaultValue'.</br>
591 	 *                            It must at least contain all the settable properties.
592 	 * @throws BeanLikeTesterException if the test fails.
593 	 */
594 	public void testToString(PropertiesAndValues propsWithDefaultValue, PropertiesAndValues propsWithOtherValue) {
595 		verifyContainsAtLeastAllMutableProperties(propsWithDefaultValue.keySet());
596 		verifyContainsAtLeastAllMutableProperties(propsWithOtherValue.keySet());
597 		verifyAllValuesFromMutablePropsAreDifferent(propsWithDefaultValue, propsWithOtherValue);
598 
599 		final Object defaultObj = createObjectWithDefaultValues(propsWithDefaultValue);
600 		final String toStringFromDefaultValues = defaultObj.toString();
601 
602 		for (final Entry<String, Object> entry : propsWithOtherValue.entrySet()) {
603 			final String property = entry.getKey();
604 			final Object value = entry.getValue();
605 			// Test only the mutable properties.
606 			if (mutablePropertyNames.contains(property)) {
607 				final Object object = createObjectWithSecificPropertySet(propsWithDefaultValue, property, value);
608 				final String toStringFromOtherValues = object.toString();
609 				if (!areValuesDifferent(toStringFromDefaultValues, toStringFromOtherValues)) {
610 					throw new BeanLikeTesterException("The result of toString() should depend on the property '" + property + "'");
611 				}
612 			}
613 		}
614 	}
615 
616 	/**
617 	 * Run all the tests:
618 	 * <ul>
619 	 * <li>{@link #testDefaultValues(PropertiesAndValues)}</li>
620 	 * <li>{@link #testMutatorsAndAccessors(PropertiesAndValues, PropertiesAndValues)}</li>
621 	 * <li>{@link #testEqualsAndHash(PropertiesAndValues, PropertiesAndValues)}</li>
622 	 * <li>{@link #testToString(PropertiesAndValues, PropertiesAndValues)}</li>
623 	 * </ul>
624 	 * 
625 	 * @param propsWithDefaultValue Property names (keys) and their value.<br/>
626 	 *                              It must at least contain all the settable properties.
627 	 * @param propsWithOtherValue Property names (keys) and their value different from 'propsWithValue'.</br>
628 	 *                            It must at least contain all the settable properties.
629 	 * @throws BeanLikeTesterException if the test fails.
630 	 */
631 	public void testBeanLike(PropertiesAndValues propsWithDefaultValue, PropertiesAndValues propsWithOtherValue) {
632 		testDefaultValues(propsWithDefaultValue);
633 		testMutatorsAndAccessors(propsWithDefaultValue, propsWithOtherValue);
634 		testEqualsAndHash(propsWithDefaultValue, propsWithOtherValue);
635 		testToString(propsWithDefaultValue, propsWithOtherValue);
636 	}
637 
638 }