How do you test equals and hashCode?

Home  >>  JUnit  >>  How do you test equals and hashCode?

How do you test equals and hashCode?

On June 25, 2012, Posted by , In JUnit, With 7 Comments

Just recently I had to implement equals() and hashCode() for several classes. With the aid of your favourite IDE, generating these methods is a matter of seconds.
Well, if there weren’t the missing tests. As I tend to be obsessive when it comes to tests, I also want to have tests for equals() and hashCode(). Only then I am able to refactor the generated code to align it with the projects code style and make sure not to break it thereby.

After being half-way through writing tests for the second class that implemented equals() and hashCode(), and more and more copying code from the existing tests and getting more and more impatient with the progress I made, I decided to write a helper class that would hopefully reduce the redundant test code.

A few iterations later I had an EqualsTester class. The general idea is to feed its assert-methods with probes that are known to be equal or unequal. They will then ensure that equals() and hashCode() work like specified as far as possible. If testing a probe fails, an AssertionError will be thrown. This is best illustrated with a code snippet:

  @Test
  public void testEqualsAndHashCode() {
    EqualsTester<Point> equalsTester = newInstance( new Point( 1, 2 ) );
    equalsTester.assertEqual( new Point( 1, 2 ), new Point( 1, 2 ) );
    equalsTester.assertNotEqual( new Point( 1, 2 ), new Point( 3, 4 ) );
  }

The factory method is just there to avoid yet another pair of angle brackets. With the given argument, a number of tests are executed to ensure that the argument

  • is equal with itself (object.equals( object ) == true)
  • is not equal with null
  • is not equal with new Object()

By passing two objects that are considered equal to assertEqual(), you can not only ensure that equals() is implemented correctly but also that their hash code is the same.

assertNotEqual() makes sure that your equals() implementation does not accidentailly consider unequal objects as equal. By default assertNotEqual() will also check that hashCode() produces distinct results. This may improve the performance of hash tables. As it isn’t required by the general contract, it can of course be disabled.

This utility class helped me, thus I though it would be worth sharing. I hope that one or another will also find it useful.


Rüdiger Herrmann

Rüdiger started writing software more than 20 years ago. From the very beginning he focused on a pragmatic approach to writing and delivering software.

His interests include promoting test driven development and agile methods to deliver clean code that works.

(EMail: rherrmann@codeaffine.com)

Latest posts by Rüdiger Herrmann (see all)

7 Comments so far:

  1. Eric Jablow says:

    Is there a test framework that can mutate each non-transient instance variable of an object and check hashCode and equals?

    You should not have two assert methods in a single test method, If the first fails, the second will not be exercised.

    In fact, tests like this should usually be parametrized tests:


    @RunWith(Parametrized.class)
    public PointTest {
    private Point p1;
    private Point p2;
    private boolean equal;
    public PointTest(int x1, int y1, int x2, int y2, boolean equal) {
    this.p1 = new Point(x1, y1);
    this.p2 = new Point (x2, y2);
    this.equal = equal;
    }

    @Parameters
    public static Collection<Object[]> data() {
    return Arrays.asList(new Object[][]{
    {1, 2, 1, 2, true},
    {1, 2, 1, 3, false},
    {2, 2, 3, 2, false},
    // etc.
    });
    }

    // Test methods
    @Test
    public void testEquals() {
    Assert.assertEquals(equal, p1.equals(p2));
    }

    @Test
    public void neverEqualsNull() {
    Assert.assertNotEquals(p1, null);
    }

    @Test
    // Can be violated by accident.
    public void hashCodesNotEqual() {
    Assert.assertEquals(equal, p1.hashCode() == p2.hashCode());
    }

  2. Frank Appel says:

    Hi Eric,

    cool – I did not know about the Parameterized suite extension although I’m doing TTD know for quite a while… But to to be honest it took me a while to understand the test case. Maybe it will take some time to get used to the concept, but at the moment it does not really convince me.

    In general I think the discussion about ‘how many asserts per test are allowed’ is not decided yet if you looking at the discussions on the web. I tend to go with Robert C. Martin (Clean Code) who seems to favour the ‘Single Concept per Test’ approach. Testing one logical CONCEPT per test means that you can have multiple asserts in one test method. In his book Robert C. Martin also provides an example of testing a SerialDate type that provides accessors to dayOfMonth, month and year and explains how this can be seen as one concept that can be testet with more than one asserts per test method.

    Greetings
    Frank

  3. Chris lutje Spelberg says:

    I use EqualsVerifier combined with Spring classpath scanning to automatically test all JPA entities (classes with the @Entity annotation)
    This works quite ok!

    Google Code: http://code.google.com/p/equalsverifier/

  4. Rüdiger Herrmann says:

    Hi Chris,
    thanks for sharing the link. I wasn’t aware of this library. It looks very comprehensive.

  5. a good EqualsTester object will test the contract of equals. The contract is equality is: reflexive, symmetric, transitive, consistent. You have one or two of these covered (by accident though). I suggest you add explicit checks for these. And be sure that the error message on a failure explains which part of the contract was broken.

    However, really I suggest using Lombok’s @EqualsAndHashcode and forgetting about writing tests like this.

  6. Rüdiger Herrmann says:

    Hi Hamlet,
    thanks for pointing at this. The code snippet doesn’t show how transitivity is ensured. It was left out for brevity, though I now think that I should add it. If you look at the EqualsTester code however, you’ll see that it is covered.
    If anything else slipped through I’d be glad to know.

  7. mestachs says:

    I’m using openpojo with a modified tester to verify getter/setter/toString/hashCode/equals

    http://mestachs.wordpress.com/2012/06/16/personal-taste-for-testing-toolbox/#openpojo

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>