Why should I use EqualsVerifier?

I have worked on many different Java projects, and when it is time to unit test an equals method, most projects fall into one of two categories. The first category has strong coverage rules, so they write several pages of tests for a single equals method, just to achieve 100% coverage on it. The second category of projects doesn’t have the requirement. They write maybe one or two tests to check the happy path. Sometimes not even that, because why test something that’s generated by the IDE anyway?

I believe that if something is worth doing, it’s worth doing it right. equals has a bunch of requirements written in its Javadoc, and they’re there for a reason. If you don’t get them right, you might run into weird problems that are hard to debug. For instance, set.contains(foo) might return false on an object that you just added to the set. WTF?

The IDE won’t save you either. You might add a field to the class and forget to regenerate equals, for example. Or you might make a subclass and override equals there, too. Suddenly super.equals(sub) returns false and sub.equals(super) returns true, even though your IDE generated both equals methods and therefore they should be correct. WTF?

So you want to get it right, but you don’t want to write pages upon pages of tests for a single equals method either.

Enter EqualsVerifier. With EqualsVerifier, your test becomes a one-liner:

EqualsVerifier.forClass(Foo.class).verify();

You get 100% coverage and the confidence that all the requirements of the equals contract are tested. Not only that, but hashCode as well. What’s not to love?

There is one downside to this. EqualsVerifier is an opinionated tool. It has to be. Unfortunately, that means it can feel restrictive to some people:

Tweet by JB Rainsberger saying: "All right. Goodbye. EqualsVerifier. I cannot handle your needless constraints."

This manual is an attempt to explain these constraints are necessary. It also explains how you can remove the constraints if you don’t agree with them. If you still feel EqualsVerifier is too restrictive, then that’s fine; you don’t have to use it if you don’t want. But if you do, the quality of your equals methods will improve, and so will your test coverage.

What does EqualsVerifier do?

EqualsVerifier tests your equals and hashCode methods by calling them repeatedly with different values.

It checks the following properties:

  • Preconditions for EqualsVerifier itself (like: did the fields specified in withIgnoredFields actually exist?)
  • The five properties of the equals contract:
    • Reflexivity
    • Symmetry
    • Transitivity
    • Consistency
    • “Non-nullity”
  • The same five properties within an inheritance hierarchy (if applicable)
  • The hashCode contract
  • That equals and hashCode are defined in terms of the same fields
  • That equals has the correct signature, so it actually overrides equals instead of overload it
  • That the fields of the class under test are final (this is important for consistency)
  • That the class under test, or its equals and hashCode methods, are final (this is important for symmetry and transitivity in inheritance hierarchies)
  • That Arrays.equals or Arrays.deepEquals are used for array fields
  • That Float.compare or Double.compare are used for float and double fields

It also gives you 100% coverage on sensible implementation of equals and hashCode. I say ‘sensible’ because it’s always possible to fool EqualsVerifier if you really want to 😉.

How does EqualsVerifier work?

The way EqualsVerifier achieves this, is through a lot of reflection and a little bit of bytecode manipulation.

First, it creates an instance of your class, without calling the constructor, in the same way that mocking frameworks do. This gives an object where all the fields are 0 or null. If the class isn’t final, it also generates a subclass for the class to test with. Then, EqualsVerifier invents values for all the fields, and assigns these using reflection.

Next, EqualsVerifier calls equals and hashCode repeatedly on various permutations of these objects to see if they return the values it expects.

Finally, it also uses reflection to look at the signature of equals, to see if it actually overrides equals instead of overload it.

Updated: