org.meanbean.test.HashCodeMethodTester Maven / Gradle / Ivy
package org.meanbean.test;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.meanbean.bean.info.BeanInformationFactory;
import org.meanbean.bean.info.JavaBeanInformationFactory;
import org.meanbean.factories.FactoryCollection;
import org.meanbean.factories.FactoryRepository;
import org.meanbean.factories.equivalent.EquivalentPopulatedBeanFactory;
import org.meanbean.factories.util.BasicFactoryLookupStrategy;
import org.meanbean.factories.util.FactoryLookupStrategy;
import org.meanbean.lang.EquivalentFactory;
import org.meanbean.util.AssertionUtils;
import org.meanbean.util.RandomValueGenerator;
import org.meanbean.util.SimpleRandomValueGenerator;
import org.meanbean.util.SimpleValidationHelper;
import org.meanbean.util.ValidationHelper;
/**
*
* Provides a means of testing the correctness of the hashCode logic implemented by a type, based solely on the
* provision of the type, with respect to:
*
*
*
* - the general hashCode contract
*
*
*
* The following is tested:
*
*
*
* - that logically equivalent objects have the same hashCode
*
* - the consistent item of the hashCode contract - the hashCode of an object should remain
* consistent across multiple invocations, so long as the object does not change
*
*
*
* Use the tests provided by this class (namely, testHashCode()
) to test a class that overrides
* hashCode()
and equals()
.
*
*
*
* As an example, to test the hashCode logic implemented by a class called MyClass do the following:
*
*
*
* HashCodeMethodTester tester = new HashCodeMethodTester();
* tester.testHashCodeMethod(MyClass.class);
*
*
*
* To test the hashCode logic implemented by a class called MyClass without a no-argument constructor do the following:
*
*
*
* HashCodeMethodTester tester = new HashCodeMethodTester();
* tester.testHashCodeMethod(new Factory<MyClass>() {
* @Override
* public MyClass create() {
* MyClass result = new MyClass("TEST_NAME");
* return result;
* }
* });
*
*
*
* The Factory creates new logically equivalent instances of MyClass. MyClass has overridden
* equals()
and hashCode()
. In the above example, there is only one property, name, which is
* considered by MyClass's hashCode logic.
*
*
* @author Graham Williamson
*/
public class HashCodeMethodTester {
/** Logging mechanism. */
private final Log log = LogFactory.getLog(HashCodeMethodTester.class);
/** Input validation helper. */
private final ValidationHelper validationHelper = new SimpleValidationHelper(log);
/** Random number generator used by factories to randomly generate values. */
private final RandomValueGenerator randomValueGenerator = new SimpleRandomValueGenerator();
/** The collection of test data Factories. */
private final FactoryCollection factoryCollection = new FactoryRepository(randomValueGenerator);
/** Provides a means of acquiring a suitable Factory. */
private final FactoryLookupStrategy factoryLookupStrategy = new BasicFactoryLookupStrategy(factoryCollection,
randomValueGenerator);
/** Factory used to gather information about a given bean and store it in a BeanInformation object. */
private final BeanInformationFactory beanInformationFactory = new JavaBeanInformationFactory();
/**
*
* Test that the hashCode logic implemented by the type the specified factory creates is correct by testing:
*
*
*
* - that logically equivalent objects have the same hashCode
*
* - the consistent item of the hashCode contract - the hashCode of an object should remain
* consistent across multiple invocations, so long as the object does not change
*
*
*
* If the test fails, an AssertionError is thrown.
*
*
* @param factory
* An EquivalentFactory that creates non-null logically equivalent objects that will be used to test
* whether the equals logic implemented by the type is correct. The factory must create logically
* equivalent but different actual instances of the type upon each invocation of create()
in
* order for the test to be meaningful and correct.
*
* @throws IllegalArgumentException
* If the specified factory is deemed illegal. For example, if it is null
, if it creates a
* null
object or if it creates objects that are not logically equivalent.
* @throws AssertionError
* If the test fails.
*/
public void testHashCodeMethod(EquivalentFactory> factory) throws IllegalArgumentException, AssertionError {
log.debug("testHashCodeMethod: Entering with factory=[" + factory + "].");
validationHelper.ensureExists("factory", "test hash code method", factory);
testHashCodesEqual(factory);
testHashCodeConsistent(factory);
log.debug("testHashCodeMethod: Exiting - Equals is correct.");
}
/**
*
* Test that the hashCode logic implemented by the specified type is correct by testing:
*
*
*
* - that logically equivalent objects have the same hashCode
*
* - the consistent item of the hashCode contract - the hashCode of an object should remain
* consistent across multiple invocations, so long as the object does not change
*
*
*
* If the test fails, an AssertionError is thrown.
*
*
* @param clazz
* The type to test the equals logic of.
*
* @throws IllegalArgumentException
* If the specified clazz is deemed illegal. For example, if it is null
.
* @throws AssertionError
* If the test fails.
*/
public void testHashCodeMethod(Class> clazz) throws IllegalArgumentException, AssertionError {
log.debug("testHashCodeMethod: Entering with clazz=[" + clazz + "].");
validationHelper.ensureExists("clazz", "test hash code method", clazz);
EquivalentPopulatedBeanFactory factory =
new EquivalentPopulatedBeanFactory(beanInformationFactory.create(clazz), getFactoryLookupStrategy());
testHashCodeMethod(factory);
log.debug("testHashCodeMethod: Exiting - HashCode is correct.");
}
/**
*
* Test that the hashCode logic implemented by the type the specified factory creates returns equal hashCodes for
* logically equivalent objects.
*
*
*
* If the test fails, an AssertionError is thrown.
*
*
* @param factory
* An EquivalentFactory that creates non-null logically equivalent objects that will be used to test
* whether the hashCode logic implemented by the type returns equal hashCodes for logically equivalent
* objects. The factory must create logically equivalent but different actual instances of the type upon
* each invocation of create()
in order for the test to be meaningful.
*
* @throws IllegalArgumentException
* If the specified factory is deemed illegal. For example, if it is null
, if it creates a
* null
object or if it creates objects that are not logically equivalent.
* @throws AssertionError
* If the test fails.
*/
protected void testHashCodesEqual(EquivalentFactory> factory) throws IllegalArgumentException, AssertionError {
log.debug("testHashCodesEqual: Entering with factory=[" + factory + "].");
validationHelper.ensureExists("factory", "test hash codes equal for equal objects", factory);
Object x = factory.create();
Object y = factory.create();
log.debug("testHashCodesEqual: Created objects x=[" + x + "] and y=[" + y + "] for test.");
validationHelper.ensureExists("factory-created object", "test hash codes equal for equal objects", x);
validationHelper.ensureExists("factory-created object", "test hash codes equal for equal objects", y);
if (!x.equals(y)) {
String message =
"Cannot test hash codes equal for equal objects if objects that should be equal are not considered logically equivalent.";
log.debug("testHashCodesEqual: " + message + " Throw IllegalArgumentException.");
throw new IllegalArgumentException(message);
}
if (x.equals(y) && x.hashCode() != y.hashCode()) {
log.debug("testHashCodesEqual: HashCodes are not the same for equal objects.");
AssertionUtils.fail("hashCodes are not the same for equal objects.");
}
log.debug("testHashCodesEqual: Exiting - Equals is correct.");
}
/**
*
* Test that the hashCode logic implemented by the type the specified factory creates is consistent with the
* consistent item of the hashCode contract.
*
*
*
* If the test fails, an AssertionError is thrown.
*
*
* @param factory
* An EquivalentFactory that creates non-null logically equivalent objects that will be used to test
* whether the hashCode logic implemented by the type is consistent with the consistent item of the
* hashCode contract. The factory must create logically equivalent but different actual instances of the
* type upon each invocation of create()
in order for the test to be meaningful.
*
* @throws IllegalArgumentException
* If the specified factory is deemed illegal. For example, if it is null
or if it creates
* a null
object.
* @throws AssertionError
* If the test fails.
*/
protected void testHashCodeConsistent(EquivalentFactory> factory) throws IllegalArgumentException, AssertionError {
log.debug("testHashCodeConsistent: Entering with factory=[" + factory + "].");
validationHelper.ensureExists("factory", "test hash code consistent item", factory);
Object x = factory.create();
log.debug("testHashCodeConsistent: Created object x=[" + x + "] for test.");
validationHelper.ensureExists("factory-created object", "test hash code consistent item", x);
int hashCode = x.hashCode();
for (int idx = 0; idx < 100; idx++) {
if (x.hashCode() != hashCode) {
log.debug("testHashCodeConsistent: HashCode is not consistent on invocation [" + idx + "].");
AssertionUtils.fail("hashCode is not consistent on invocation [" + idx + "].");
}
}
log.debug("testHashCodeConsistent: Exiting - Equals is correct.");
}
/**
* Get a RandomValueGenerator.
*
* @return A RandomValueGenerator.
*/
public RandomValueGenerator getRandomValueGenerator() {
return randomValueGenerator;
}
/**
* Get the collection of test data Factories with which you can register new Factories for custom Data Types.
*
* @return The collection of test data Factories.
*/
public FactoryCollection getFactoryCollection() {
return factoryCollection;
}
/**
* Get the FactoryLookupStrategy, which provides a means of acquiring Factories.
*
* @return The factory lookup strategy.
*/
public FactoryLookupStrategy getFactoryLookupStrategy() {
return factoryLookupStrategy;
}
}