Hi Lars,
First of all, I have to admit, the use case of a class that doesn't override equals/hashCode is probably not handled optimally in EqualsVerifier. After all, why would you test the equals method of a class that doesn't have one? You have a point, maybe EqualsVerifier should simply fail when equals/hashCode aren't overridden.
On the other hand, the equals method that is inherited from Object is not wrong, and why should EV fail if it's not wrong? Also, I have to consider backward compatibility; I don't want existing tests to suddenly start failing because of this. So, this is something that I will have to think about.
Now for your actual question.
The difference comes from the way instanceof and getClass() behave when inheritance comes into the mix. In its default behaviour, EqualsVerifier will use some bytecode manipulation trickery to construct an "anonymous" subclass of the class you're testing, to see if they can be equal to each other. The subclass basically looks like this:
public class SubDummy extends Dummy {
public Dummy(String name) {
super(name);
}
}
Then, EV instantiates two objects, essentially like this:
Dummy a = new Dummy("myname");
Dummy b = new SubDummy("myname");
Then, when a.equals(b) is called, of course this returns false, because the equals method inherited from Object simply checks if the hashCodes are equal, and they aren't. But unless you specify usingGetClass(), EV assumes that you use an instanceof check in your equals method. This implies that a Dummy object should be equal to an instance of SubDummy, if their fields have the same values, which they do. (This is necessary, for example, when you use Hibernate, which also uses some bytecode manipulation to construct anonymous subclasses of your entity classes. You still want those to be able to be equal to the objects that you instantiate yourself.) In other words, EqualsVerifier assumes that a and b should be equal, and they're not. That's why your second test fails.
When you use getClass() in your equals method, you basically say that subclasses can never be equal. EqualsVerifier knows this, and won't even try to check the subclass--and that's why your first test succeeds.
Also, the call to allFieldsShouldBeUsed() has no effect here. You haven't overridden equals, so every Dummy object is un-equal to every other Dummy object, and therefore you have tricked EV into believing that changing the value of a field also influences the outcome of equals and hashCode().
Hope this helps!
Regards,
Jan
On 7 September 2012 15:56, Lars Briem
<briem...@googlemail.com> wrote:
Hello,
I am a bit confused about the effects of usingGetClass. In the following class equals and hashCode are not overridden.
public class Dummy {
private final String name;
public Dummy(final String name) {
this.name = name;
}
}
When I now write tests for this class I expect different behaviour in two tests which I am not able to explain.
Test 1:
EqualsVerifier.forClass(Dummy.class).allFieldsShouldBeUsed().usingGetClass().verify();
Test 2:
EqualsVerifier.forClass(Dummy.class).allFieldsShouldBeUsed().verify();
The first test runs without any failure, but the second one fails with this error:
java.lang.AssertionError: Subclass: object is not equal to an instance of a trivial subclass with equal fields:
test.equalsverifier.Dummy@f0c85e
Consider making the class final.
For more information, go to: http://code.google.com/p/equalsverifier/wiki/ErrorMessages
at nl.jqno.equalsverifier.util.Assert.fail(Assert.java:96)
at nl.jqno.equalsverifier.EqualsVerifier.handleError(EqualsVerifier.java:359)
at nl.jqno.equalsverifier.EqualsVerifier.verify(EqualsVerifier.java:336)
at test.equalsverifier.DummyTest.failingTest(DummyTest.java:15)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Where does this difference come from?
I think the different behaviour of the two test cases is wrong. In my opinion the first test tells me, that I do not have to override the equals and hashCode methods which indeed is not correct. So EqualsVerifier should fail in both cases.
Is there something wrong in EqualsVerifier or do I just not understand why this behaviour is correct?
Best regards,
Lars Briem