80

In a nutshell, the hashCode contract, according to Java's object.hashCode():

  1. The hash code shouldn't change unless something affecting equals() changes
  2. equals() implies hash codes are ==

Let's assume interest primarily in immutable data objects - their information never changes after they're constructed, so #1 is assumed to hold. That leaves #2: the problem is simply one of confirming that equals implies hash code ==.

Obviously, we can't test every conceivable data object unless that set is trivially small. So, what is the best way to write a unit test that is likely to catch the common cases?

Since the instances of this class are immutable, there are limited ways to construct such an object; this unit test should cover all of them if possible. Off the top of my head, the entry points are the constructors, deserialization, and constructors of subclasses (which should be reducible to the constructor call problem).

[I'm going to try to answer my own question via research. Input from other StackOverflowers is a welcome safety mechanism to this process.]

[This could be applicable to other OO languages, so I'm adding that tag.]

Paul Brinkley
  • 6,283
  • 3
  • 24
  • 33
  • 1
    I usually find that the contract already broke due to either implementing equals or hashcode, but not both. There openpojo helps in a Java project. The EqualsAndHashCodeMatchRule helps with that. The already existing answers provide enough details regarding testing the rest of the contract. – Michiel Leegwater Aug 01 '19 at 07:11

8 Answers8

75

EqualsVerifier is a relatively new open source project and it does a very good job at testing the equals contract. It doesn't have the issues the EqualsTester from GSBase has. I would definitely recommend it.

Community
  • 1
  • 1
Benno Richters
  • 15,378
  • 14
  • 42
  • 45
  • 8
    Just _using_ EqualsVerifier taught me something about Java! – David Nov 07 '14 at 20:49
  • Am I crazy? EqualsVerifier didn't seem to check Value Object semantics at all! It thinks my class correctly implements equals(), but my class uses the default "==" behavior. How can this be?! I must be missing something simple. – J. B. Rainsberger Nov 03 '15 at 13:27
  • Aha! If I don't override equals(), then EqualsVerifier assumes that everything is OK!? That's not what I would expect. I would expect the same behavior for not overriding equals() as for overriding equals() to do exactly the same thing super.equals(). Weird. – J. B. Rainsberger Nov 03 '15 at 14:32
  • ...and I abandon EqualsVerifier. Too many needless surprises. If I change my implementation to make instances identical, then EqualsVerifier whines that some of my instances are identical (even though others aren't, but should be equal). THAT SHOULD BE OK. I can't imagine why that would be the default behavior. Here's what I want: "Here are some objects that should be equal; please check that they are. Thx. Here are some objects that should not be equal; please check that they aren't. Thx." – J. B. Rainsberger Nov 03 '15 at 14:53
  • 7
    @J.B.Rainsberger Hi! Creator of EqualsVerifier here. I'm sorry that you find it confusing. Re: not overriding equals: you can enable your expected behavior with "allFieldsShouldBeUsed()". This will be the default in the next version, for the reasons you state. Re: identical instances: I don't think I can help you without seeing some example code. Can you please elaborate in a new question, or on the EV mailinglist at https://groups.google.com/forum/?fromgroups#!forum/equalsverifier ? Thanks! – jqno Nov 03 '15 at 21:35
  • 1
    EqualsVerifier is really powerfull. I gained a huge amount of time by not testing each potential combination of not equals objects. The error messages are really precise and instructive. And the verifier is very configurable if your coding convention are different from the default expectation. Great job @jqno – gontard Mar 07 '16 at 12:07
13

My advice would be to think of why/how this might ever not hold true, and then write some unit tests which target those situations.

For example, let's say you had a custom Set class. Two sets are equal if they contain the same elements, but it's possible for the underlying data structures of two equal sets to differ if those elements are stored in a different order. For example:

MySet s1 = new MySet( new String[]{"Hello", "World"} );
MySet s2 = new MySet( new String[]{"World", "Hello"} );
assertEquals(s1, s2);
assertTrue( s1.hashCode()==s2.hashCode() );

In this case, the order of the elements in the sets might affect their hash, depending on the hashing algorithm you've implemented. So this is the kind of test I'd write, since it tests the case where I know it would be possible for some hashing algorithm to produce different results for two objects I've defined to be equal.

You should use a similar standard with your own custom class, whatever that is.

Eli Courtwright
  • 186,300
  • 67
  • 213
  • 256
6

It's worth using the junit addons for this. Check out the class EqualsHashCodeTestCase http://junit-addons.sourceforge.net/ you can extend this and implement createInstance and createNotEqualInstance, this will check the equals and hashCode methods are correct.

4

I would recommend the EqualsTester from GSBase. It does basically what you want. I have two (minor) problems with it though:

  • The constructor does all the work, which I don't consider to be good practice.
  • It fails when an instance of class A equals to an instance of a subclass of class A. This is not necessarily a violation of the equals contract.
Benno Richters
  • 15,378
  • 14
  • 42
  • 45
2

[At the time of this writing, three other answers were posted.]

To reiterate, the aim of my question is to find standard cases of tests to confirm that hashCode and equals are agreeing with each other. My approach to this question is to imagine the common paths taken by programmers when writing the classes in question, namely, immutable data. For example:

  1. Wrote equals() without writing hashCode(). This often means equality was defined to mean equality of the fields of two instances.
  2. Wrote hashCode() without writing equals(). This may mean the programmer was seeking a more efficient hashing algorithm.

In the case of #2, the problem seems nonexistent to me. No additional instances have been made equals(), so no additional instances are required to have equal hash codes. At worst, the hash algorithm may yield poorer performance for hash maps, which is outside the scope of this question.

In the case of #1, the standard unit test entails creating two instances of the same object with the same data passed to the constructor, and verifying equal hash codes. What about false positives? It's possible to pick constructor parameters that just happen to yield equal hash codes on a nonetheless unsound algorithm. A unit test that tends to avoid such parameters would fulfill the spirit of this question. The shortcut here is to inspect the source code for equals(), think hard, and write a test based on that, but while this may be necessary in some cases, there may also be common tests that catch common problems - and such tests also fulfill the spirit of this question.

For example, if the class to be tested (call it Data) has a constructor that takes a String, and instances constructed from Strings that are equals() yielded instances that were equals(), then a good test would probably test:

  • new Data("foo")
  • another new Data("foo")

We could even check the hash code for new Data(new String("foo")), to force the String to not be interned, although that's more likely to yield a correct hash code than Data.equals() is to yield a correct result, in my opinion.

Eli Courtwright's answer is an example of thinking hard of a way to break the hash algorithm based on knowledge of the equals specification. The example of a special collection is a good one, as user-made Collections do turn up at times, and are quite prone to muckups in the hash algorithm.

Paul Brinkley
  • 6,283
  • 3
  • 24
  • 33
1

You can also use something similar to http://code.google.com/p/guava-libraries/source/browse/guava-testlib/src/com/google/common/testing/EqualsTester.java to test equals and hashCode.

Mangoose
  • 922
  • 1
  • 9
  • 17
1

This is one of the only cases where I would have multiple asserts in a test. Since you need to test the equals method you should also check the hashCode method at the same time. So on each of your equals method test cases check the hashCode contract as well.

A one = new A(...);
A two = new A(...);
assertEquals("These should be equal", one, two);
int oneCode = one.hashCode();
assertEquals("HashCodes should be equal", oneCode, two.hashCode());
assertEquals("HashCode should not change", oneCode, one.hashCode());

And of course checking for a good hashCode is another exercise. Honestly I wouldn't bother to do the double check to make sure the hashCode wasn't changing in the same run, that sort of problem is better handled by catching it in a code review and helping the developer understand why that's not a good way to write hashCode methods.

Rob Spieldenner
  • 1,697
  • 1
  • 16
  • 26
0

If I have a class Thing, as most others do I write a class ThingTest, which holds all the unit tests for that class. Each ThingTest has a method

 public static void checkInvariants(final Thing thing) {
    ...
 }

and if the Thing class overrides hashCode and equals it has a method

 public static void checkInvariants(final Thing thing1, Thing thing2) {
    ObjectTest.checkInvariants(thing1, thing2);
    ... invariants that are specific to Thing
 }

That method is responsible for checking all invariants that are designed to hold between any pair of Thing objects. The ObjectTest method it delegates to is responsible for checking all invariants that must hold between any pair of objects. As equals and hashCode are methods of all objects, that method checks that hashCode and equals are consistent.

I then have some test methods that create pairs of Thing objects, and pass them to the pairwise checkInvariants method. I use equivalence partitioning to decide what pairs are worth testing. I usually create each pair to be different in only one attribute, plus a test that tests two equivalent objects.

I also sometimes have a 3 argument checkInvariants method, although I finds that is less useful in findinf defects, so I do not do this often

Raedwald
  • 46,613
  • 43
  • 151
  • 237