Coverage is not 100%
EqualsVerifier should get 100% code coverage on your equals
and hashCode
methods. However, it can happen that it doesn’t achieve this. Below is a list of cases where it’s impossible to get 100% coverage, and their solutions.
If you have an example of a class where EqualsVerifier doesn’t give you 100% coverage, and it’s not in the list below, please let me know.
Mutation coverage
You can measure mutation coverage using a tool like PITest. However, EqualsVerifier will not get 100% mutation coverage on hashCode
. Mutation testing will change operators like *
and +
in the hashCode
implementation into other operators and check if the tests then fail, which they often don’t. The only way to get 100% mutation coverage, is to “pin down” the hashCode to a specific value, which is a form of overfitting. Therefore, it’s not desitable to have 100% mutation coverage on hashCode
.
Using Lombok
Lombok always generates null checks in its equals
methods, even if there is an @Nonnull
annotation. For example:
This class will have less than 100% coverage, because null checks are generated for the s
field. EqualsVerifier will not check these paths, due to the @Nonnull
annotation.
If you run into this problem, you can tell EqualsVerifier to ignore the annotation, like this:
Using canEqual
If you have a hierarchy of classes that each redefine equals
and hashCode
as described in this article, the leaf nodes in your class hierarchy tree won’t get 100% percent coverage.
Say that you have a hierarchy of Point
classes, where Point
has fields x
and y
, and its subclass ColorPoint
adds a field color
. ColorPoint
’s canEqual
method looks like this:
And its equals
method looks like this:
Now, the return false;
statement after the call to other.canEqual(this)
will get no coverage. This makes sense, because it can only be reached if other.canEqual(this)
returns false, which it will never do. Because of the instanceof
check in equals
, canEqual
will always be called on a (subclass of) ColorPoint
. But ColorPoint
is a leaf node, so there are no subclasses. other
will always be exactly of type ColorPoint
, and other.canEqual(this)
will always return true.
There are two ways to work around this, and still get 100% coverage:
- Simply remove the
canEqual
check in the leaf nodes. I don’t recommend this, because it’s risky if you later decide to add subclasses toColorPoint
; then it will no longer be a leaf node in the tree. If you forget to put back the call tocanEqual
, you may run into problems. - Invent a subclass specifically for your test. It’s verbose, and
ColorPoint
can no longer be markedfinal
, but it’s safe and correct. Your test will now look like this:
Note that this issue is not specific to EqualsVerifier; with hand-written equals
test code you would run into exactly the same problem.
Non-standard equality code
Another way that EqualsVerifier won’t reach 100% code coverage, is with non-standard equality code. For example, given a Point
class with fields x
and y
:
EqualsVerifier will never execute the second if
statement’s block, simply because it doesn’t test for all possible values. It can’t; it would take far too long. (For our Point
class, assuming that x
and y
are int
, it would take more than 232*232 comparisons.)
Of course, this is a contrived example (and many more are possible, for example using random numbers or environment variables). However, if you really do need non-standard branches in your equals
method, you will have to test them manually; not just because EqualsVerifier doesn’t cover them, but because EqualsVerfier also doesn’t test them.