blob: 5ed0e24f0ddb444e5362cdab5a5f27d8e1b863e8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 by SAP AG, Walldorf.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SAP AG - initial API and implementation
*******************************************************************************/
package org.eclipse.jst.ws.jaxws.testutils.jmock.testcases;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import junit.framework.TestCase;
/**
* An abstract test case for the <c>Object.equals(Object)</c> and <c>Object.hashCode()</c> methods. This test case performs the tests for <c>equals</c>
* and <c>hashCode</c> simultaniesly as they are tightly connected. The tescase test the following properties of equals/hasCode:
* <p>
* 1. equals reflexivity 2. equals symetricity 3. equals transitivyte 4. hashCode statbility 5. hashCode immutability 6. equals is suitable for
* subclasses
* </p>
*
* The test cases is abstract and need to be sublcassed by concrete test case. Several abstract methods need to be implemented by concrete test cases.
*
* @author Hristo Sabev
*
* @param <T>
*/
public abstract class EqualsTestCase<T> extends TestCase
{
private final Class<T> testClass;
/**
* Creates a new <c>EqualsTestCase</c> instance to test the equals method of the supplied class
*
* @param testClass -
* the class whose equals method will be tested
*/
public EqualsTestCase(Class<T> testClass)
{
this(null, testClass);
}
/**
* Creates a new <c>EqualsTestCase</c> instance to test the equals method of the supplied class. This test case also takes a name paramter, which
* i passed to <c>TestCase</c> super constructor. I.e. this is the name of the test case
*
* @param name -
* name of the test case
* @param testClass -
* the class whose equals method will be tested
*/
public EqualsTestCase(String name, Class<T> testClass)
{
assertNotNull("test class passed in constructor was null", testClass);
this.testClass = testClass;
}
/**
* Obtains a new iterator of instances for the tested class.
*
* @param iterations -
* number of iterations i.e the number of times one can invoke <c>Iterator.next()</c> withouth getting <c>NoSuchElementException</c>
* exception
* @return a new iterator of instanced for the tested class;
*/
abstract public Iterator<T> newObjectIterator(int iterations);
/**
* Obtains a new instance of the tested class. Each call to this method should return a new instance, which is considered equal to the instance
* returned by the prefvious call but is not identical. For example if the tested class was <c>String</c> the most obvious implementation of this
* method would be <c>
* <p>
* return new String("test string")
* </p>
* </c>
*
* @return a new instance of the test class assumed to be equal to the previously returned instances
*/
abstract public T newEqualInstance();
/**
* Obtains a new instance of a single class, which is a subclass of the tested class. The subclass must not override the <c>equals</c> method of
* the tested class. All instances should be equal. The returned instances should have the same values of all the inherited fields as the
* instances retured by <c>newEqualInstance</c>
*
* @return returns a new instance of single sublcass of the tested class. Each call should return an instance equal to the previously returned but
* not identical. Null if the tested class is final
*/
abstract public T newAncestorEqualInstance();
/**
* Obtains a new instance of the the tested class. The single restriction on the returned instances is that they should not be equal to the
* instances returned by <c>newEqualInstance</c>
*
* @return a new instance of the test class. The newly returned instance is not equal to the instances returned by <c>newEqualInstance</c>
*/
abstract public T newNonEqualInstance();
/**
* Modifies a given instance of the tested class. The instance should be modified in such a way that it's not equal to the instances retured by
* <c>newEqualInstance</c> ans <c>newNoneEqualInstance()</c>. If the tested class is immutable then the implementation of this method should be
* empty
*
* @param instance -
* the instance to be modified.
*/
abstract public void modifyObjectInstance(T instance);
/**
* Obtains the number of comparisons that will be made for the reflexivity test. This method can be overriden by subclasses to specify a different
* value. By default this number is 100.
*
* @return - the number of comparisons that will be made for the reflexivity test.
*/
public int getIterationsForReflixivityTest()
{
return 100;
}
/**
* Obatains the number of comparisons that will be made for the symitricity test. This method can be override by subclasses to specify a different
* value. By default this number is 1000
*
* @return - the number of comparisons that will be made for the reflixivity test.
*/
public int getIterationsForSymetricityTest()
{
return 1000;
}
/**
* Obatains the number of comparisons that will be made for the transitivity test. This method can be override by subclasses to specify a
* different value. By default this number is 2000
*
* @return - the number of comparisons that will be made for the transitivity test.
*/
public int getIterationsForTransitivityTest()
{
return 2000;
}
/**
* Tests that equals is reflexive. It enforces the rule a.equals(a) == true. This test also tests that hashCode function is stable(). I.e.
* hashCode doesn't change it's value between several invocations, as well as that hashCode is immutable over object change. The test strategy is
* to create an new object iterator and then for each returned instance:
*
* <ol>
* <li>Compare it with itself</li>
* <li>check that its hash code stays the same</li>
* <li>modify the returned instance by calling <c>modifyInstance</c> and then check the hashCode again</li>
* </ol>
*
*/
public void testEqulsReflexive()
{
final int iterationsForThisTest = getIterationsForReflixivityTest();
final Iterator<T> objectTiterator = newObjectIterator(iterationsForThisTest);
while (objectTiterator.hasNext())
{
final T objectT = objectTiterator.next();
assertTrue("equals is not reflexive", objectT.equals(objectT));
final int initialHashCode = objectT.hashCode();
for (int i = 0; i < iterationsForThisTest; i++)
{
assertEquals("hashCode is not stable upon multiple calls", initialHashCode, objectT.hashCode());
}
modifyObjectInstance(objectT);
assertEquals("hashCode is not stable upon modification of the object", initialHashCode, objectT.hashCode());
}
}
/**
* Tests that equals is symetric i.e. a.equals(b) <==> b.equals(a). This method also tests that hashCode behaves correctly in sense a.quals(b) =>
* a.hashCode() == b.hashCode(). This test uses two object iterators. One for a and one for b, where a and b are from the above rule. Each of
* these iterators are instantiated with the same number of elements. This method also checks that whenever two instances returned by the two
* iterators are equal then their hascode is equal.
*
*/
public void testEqualsSymetric()
{
final int iterationsForThisTest = getIterationsForSymetricityTest();
final Iterator<T> objectTIterator1 = newObjectIterator(iterationsForThisTest);
final Iterator<T> objectTIterator2 = newObjectIterator(iterationsForThisTest);
while (objectTIterator1.hasNext())
{
assertTrue("The two object iterators don't have equal number of objects", objectTIterator2.hasNext());
final T objectT1 = objectTIterator1.next();
final T objectT2 = objectTIterator2.next();
if (objectT1.equals(objectT2))
{
assertTrue("Equals is not symetric", objectT2.equals(objectT1));
assertTrue("Hashcode is not the same for equal instances", objectT1.hashCode() == objectT2.hashCode());
} else if (objectT2.equals(objectT1))
{
fail("Equals is not symetric");
}
}
}
/**
* Tests that equals is transitive i.e. a.equals(b) & b.equals(c) => a.equals(c). This test uses three object iterators. One for a, one for b, and
* one for c, where a,b,c are from the above rule. Each of these iterators are instantiated with the same number of elements.
*/
public void testEqualsTransitive()
{
final int iterationsForThisTest = getIterationsForTransitivityTest();
final Iterator<T> objectTIterator1 = newObjectIterator(iterationsForThisTest);
final Iterator<T> objectTIterator2 = newObjectIterator(iterationsForThisTest);
final Iterator<T> objectTIterator3 = newObjectIterator(iterationsForThisTest);
while (objectTIterator1.hasNext())
{
assertTrue("The three object iterators don't have equal number of objects", objectTIterator2.hasNext());
assertTrue("The three object iterators don't have equal number of objects", objectTIterator3.hasNext());
final T objectT1 = objectTIterator1.next();
final T objectT2 = objectTIterator2.next();
final T objectT3 = objectTIterator3.next();
if (objectT1.equals(objectT2) && objectT1.equals(objectT3))
{
assertTrue("equals is not transitive", objectT2.equals(objectT3));
}
}
}
/**
* Tests that <c>equal</c> returns false if <c>null</c> is passed as argument
*
*/
public void testEqualsReturnsFalseIfNullPassed()
{
final T objectT = this.newEqualInstanceCheckCorrect();
assertFalse("equals returned true when null passed for other object", objectT.equals(null));
}
/**
* Tests that <c>equal</c> returns false if a non-equal object is passed as an argument. The two non equal instances are created by calling
* <c>newEqualInstance</c> and <c> newNonEqualInstance</c>
*
*/
public void testEqualsReturnsFalseOnNonEquals()
{
final T objectT1 = this.newEqualInstanceCheckCorrect();
final T objectT2 = this.newNonEqualInstanceCheckCorrect();
assertTrue("Equals reported true for instances assumed non equal.", !objectT1.equals(objectT2) && !objectT2.equals(objectT1));
}
/**
* Tests that <c>equal</cL returns true if an equal object is passed as an argument. The instances that are compared are obtained by two
* consecutive calls to <c>newEqualInstance</c>
*
*/
public void testEqualsReturnsTrueOnEquals()
{
final T objectT1 = this.newEqualInstanceCheckCorrect();
final T objectT2 = this.newEqualInstanceCheckCorrect();
assertTrue("Equals reported false for instances assumed equal.", objectT1.equals(objectT2) && objectT2.equals(objectT1));
}
/**
* Tests that equals returns false if an object of another class is passed. In case that the tested class is not final this test passes a subclass
* of the test class as an argument to <c>equals</c>. The passed ancestor object has the same value for all its field. This is intended to cahtch
* wrong implementations using "instanceof" rather than <c>getClass() == other.getClass()</c>
*
*/
public void testEqualsReturnsFalseIfOtherClassPassed()
{
final T objectT = newEqualInstanceCheckCorrect();
Object object = newAncestorEqualInstanceCheckCorrect();
if (object == null)
{
// class is final. No ancestor could be created. Use some other class.
object = new Object();
}
assertFalse("equals returned true when object of another type passed", objectT.equals(object));
}
/**
* Tests that <c>equals</c> is properly implemented for use in subclasses. It takes two equal ancestor instances and compares them for equality.
* Since the ancestor class does not override the equals method. The tests asserts that equals should return true.
*
*/
public void testEqualsSuitableForAncestors()
{
final T ancestorT1 = newAncestorEqualInstanceCheckCorrect();
final T ancestorT2 = newAncestorEqualInstanceCheckCorrect();
if (ancestorT1 == null)
{
assertNull(
"The newAncestorInstance() method returned null on the second invocation, while it return a value differet than null from the first",
ancestorT2);
return;
}
assertTrue(ancestorT1.equals(ancestorT2) && ancestorT2.equals(ancestorT1));
}
private T newAncestorEqualInstanceCheckCorrect()
{
final T ancestorT = newAncestorEqualInstance();
if (ancestorT == null)
{
assertTrue("newAncestorEqualInstance returned null, although the test class ", Modifier.isFinal(testClass.getModifiers()));
} else
{
assertTrue("newAncestorEqualInstance returned object of class " + ancestorT.getClass() + ", which is not subtype of "
+ testClass.getName(), testClass.isAssignableFrom(ancestorT.getClass()));
}
return ancestorT;
}
private T newEqualInstanceCheckCorrect()
{
final T objectT = newEqualInstance();
assertTrue("newEqualInstance returned object of class " + objectT.getClass().getName() + "Expected was class " + testClass.getName(),
testClass == objectT.getClass());
return objectT;
}
private T newNonEqualInstanceCheckCorrect()
{
final T objectT = newNonEqualInstance();
assertTrue("newNonEqualInstance returned object of class " + objectT.getClass().getName() + "Expected was class " + testClass.getName(),
testClass == objectT.getClass());
return objectT;
}
}