The following class serve as generic tester for equals/hashCode contract. It is a part of a home grown testing framework.
The class:
@Ignore @RunWith(Theories.class) public abstract class ObjectTest { // For any non-null reference value x, x.equals(x) should return true @Theory public void equalsIsReflexive(Object x) { assumeThat(x, is(not(equalTo(null)))); assertThat(x.equals(x), is(true)); } // For any non-null reference values x and y, x.equals(y) // should return true if and only if y.equals(x) returns true. @Theory public void equalsIsSymmetric(Object x, Object y) { assumeThat(x, is(not(equalTo(null)))); assumeThat(y, is(not(equalTo(null)))); assumeThat(y.equals(x), is(true)); assertThat(x.equals(y), is(true)); } // For any non-null reference values x, y, and z, if x.equals(y) // returns true and y.equals(z) returns true, then x.equals(z) // should return true. @Theory public void equalsIsTransitive(Object x, Object y, Object z) { assumeThat(x, is(not(equalTo(null)))); assumeThat(y, is(not(equalTo(null)))); assumeThat(z, is(not(equalTo(null)))); assumeThat(x.equals(y) && y.equals(z), is(true)); assertThat(z.equals(x), is(true)); } // For any non-null reference values x and y, multiple invocations // of x.equals(y) consistently return true or consistently return // false, provided no information used in equals comparisons on // the objects is modified. @Theory public void equalsIsConsistent(Object x, Object y) { assumeThat(x, is(not(equalTo(null)))); boolean alwaysTheSame = x.equals(y); for (int i = 0; i < 30; i++) { assertThat(x.equals(y), is(alwaysTheSame)); } } // For any non-null reference value x, x.equals(null) should // return false. @Theory public void equalsReturnFalseOnNull(Object x) { assumeThat(x, is(not(equalTo(null)))); assertThat(x.equals(null), is(false)); } // Whenever it is invoked on the same object more than once // the hashCode() method must consistently return the same // integer. @Theory public void hashCodeIsSelfConsistent(Object x) { assumeThat(x, is(not(equalTo(null)))); int alwaysTheSame = x.hashCode(); for (int i = 0; i < 30; i++) { assertThat(x.hashCode(), is(alwaysTheSame)); } } // If two objects are equal according to the equals(Object) method, // then calling the hashCode method on each of the two objects // must produce the same integer result. @Theory public void hashCodeIsConsistentWithEquals(Object x, Object y) { assumeThat(x, is(not(equalTo(null)))); assumeThat(x.equals(y), is(true)); assertThat(x.hashCode(), is(equalTo(y.hashCode()))); } // Test that x.equals(y) where x and y are the same datapoint // instance works. User must provide datapoints that are not equal. @Theory public void equalsWorks(Object x, Object y) { assumeThat(x, is(not(equalTo(null)))); assumeThat(x == y, is(true)); assertThat(x.equals(y), is(true)); } // Test that x.equals(y) where x and y are the same datapoint instance // works. User must provide datapoints that are not equal. @Theory public void notEqualsWorks(Object x, Object y) { assumeThat(x, is(not(equalTo(null)))); assumeThat(x != y, is(true)); assertThat(x.equals(y), is(false)); } }
usage:
import org.junit.experimental.theories.DataPoint; public class ObjectTestTest extends ObjectTest { @DataPoint public static String a = "a"; @DataPoint public static String b = "b"; @DataPoint public static String nullString = null; @DataPoint public static String emptyString = ""; }
The Contract Between equals() and hashcode() If two objects are equal according to the equals(Object) method, then calling the hashcode() method on each of the two objects must produce the same integer result.
If two objects are equal(according to equals() method) then the hashCode() method should return the same integer value for both the objects. But, it is not necessary that the hashCode() method will return the distinct result for the objects that are not equal (according to equals() method).
Java hashCode() An object hash code value can change in multiple executions of the same application. If two objects are equal according to equals() method, then their hash code must be same. If two objects are unequal according to equals() method, their hash code are not required to be different.
hashCode() method This method must be overridden in every class which overrides equals() method. Syntax : public int hashCode() // This method returns the hash code value // for the object on which this method is invoked.
One thing to consider: testing an object's conformance to the equals contract should involve instances of other types. In particular, problems are likely to appear with instances of a subclass or superclass. Joshua Bloch gives an excellent explanation of the related pitfalls in Effective Java (I'm reusing duffymo's link, so he should get credit for it) -- see the section under Transitivity involving the Point and ColorPoint classes.
True, your implementation doesn't prevent someone from writing a test that involves instances of a subclass, but because ObjectTest
is a generic class it gives the impression that all data points should come from a single class (the class being tested). It might be better to remove the type parameter altogether. Just food for thought.
Joshua Bloch lays out the contract for hash code and equals in chapter 3 of "Effective Java". Looks like you covered a great deal of it. Check the document to see if I missed anything.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With