how do I resolve a property comparison on an entity?

152 views
Skip to first unread message

Caleb Cushing

unread,
Nov 7, 2018, 5:54:42 PM11/7/18
to equalsverifier
@Override
public boolean equals( Object o ) {
    if ( this == o ) {
        return true;
    }
    if ( o instanceof AbstractEntityBase ) {
        AbstractEntityBase that = (AbstractEntityBase) o;
        @SuppressWarnings( "unchecked" )
        var equals = that.canEquals( this )
                && this.canEquals( that )
                && this.fieldEquals( (SELF) that );
        return equals;
    }
    return false;
}

/**
 * subclasses should implement checking for instanceof their class
 * @param that
 * @return
 */
protected boolean canEquals( AbstractEntityBase that ) {
    return that != null;
}

/**
 * subclasses should implement checking super || properties
 * @param that
 * @return
 */
protected boolean fieldEquals( SELF that ) {
    return !(this.isNew() && that.isNew() ) && Objects.equals( this.getId(), that.getId() );
}

@Override
public int hashCode() {
    return Objects.hashCode( this.id ) + this.hashFields();
}

protected int hashFields() {
    return 0;
}
subclass
@Override
protected boolean canEquals( AbstractEntityBase that ) {
    return that instanceof Emr;
}

@Override
protected boolean fieldEquals( Emr that ) {
    if ( super.fieldEquals( that ) ) {
        return true;
    }
    return Objects.equals( this.timestamp, that.timestamp )
            && Objects.equals( this.mrn, that.mrn )
            && Objects.equals( this.units, that.units )
            && Objects.equals( this.value, that.value )
            && Objects.equals( this.site, that.site )
            && Objects.equals( this.description, that.description )
            && ( this.isNew() && that.isNew() );
}

@Override
protected int hashFields() {
    if ( this.isNew() ) {
        return Objects.hash( timestamp, mrn, units, value, site, description );
    }
    return super.hashFields();
}
and my tests
@Test
void equalsAndHashcodeForIdentity() {
    EqualsVerifier.forClass( Emr.class )
            .withRedefinedSuperclass()
            .withOnlyTheseFields( AbstractEntityBase.ID )
            .verify();


}

@Test
void equalsAndHashcodeForFields() {
    EqualsVerifier.forClass( Emr.class )
            .withRedefinedSuperclass()
            .withIgnoredFields( AbstractEntityBase.ID )
            .verify();
}

first test passses, second does not with
java.lang.AssertionError: EqualsVerifier found a problem in class Emr.
-> Significant fields: equals does not use mrn, or it is stateless.


at nl.jqno.equalsverifier.EqualsVerifierApi.verify(EqualsVerifierApi.java:342)
at com.potrero.ph.dal.emr.EmrTest.equalsAndHashcodeForFields(EmrTest.java:24)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:436)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:170)
at org.junit.jupiter.engine.execution.ThrowableCollector.execute(ThrowableCollector.java:40)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:166)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:113)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:58)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:112)
at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$2(HierarchicalTestExecutor.java:120)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:497)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:120)
at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$2(HierarchicalTestExecutor.java:120)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)
at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:497)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:120)
at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:55)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:43)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:170)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:154)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:90)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:74)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: nl.jqno.equalsverifier.internal.exceptions.AssertionException
at nl.jqno.equalsverifier.internal.util.Assert.assertTrue(Assert.java:54)
at nl.jqno.equalsverifier.internal.checkers.fieldchecks.SignificantFieldCheck.assertFieldShouldBeIgnored(SignificantFieldCheck.java:96)
at nl.jqno.equalsverifier.internal.checkers.fieldchecks.SignificantFieldCheck.execute(SignificantFieldCheck.java:63)
at nl.jqno.equalsverifier.internal.checkers.FieldInspector.check(FieldInspector.java:27)
at nl.jqno.equalsverifier.internal.checkers.FieldsChecker.check(FieldsChecker.java:65)
at nl.jqno.equalsverifier.EqualsVerifierApi.verifyWithExamples(EqualsVerifierApi.java:416)
at nl.jqno.equalsverifier.EqualsVerifierApi.performVerification(EqualsVerifierApi.java:386)
at nl.jqno.equalsverifier.EqualsVerifierApi.verify(EqualsVerifierApi.java:339)
... 55 more


using version 3.0.2
I don't understand why this thinks it's not using mrn, is my equals/hashcode wrong (probably) or do I need to change the test more?

Jan Ouwens

unread,
Nov 9, 2018, 6:52:47 AM11/9/18
to equalsv...@googlegroups.com, Caleb Cushing
Hi Caleb,

That's a lot of code and not a lot of context ... am I understanding correctly that you have JPA class where equals is based on the id, except when it's new, in which case you look at all the fields? If so, please take a look here: https://jqno.nl/equalsverifier/manual/jpa-entities/

In any event, you shouldn't make two separate, slightly different EqualsVerifier tests for the same class. 


Jan
--
You received this message because you are subscribed to the Google Groups "equalsverifier" group.
To unsubscribe from this group and stop receiving emails from it, send an email to equalsverifie...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Caleb Cushing

unread,
Nov 9, 2018, 8:49:13 AM11/9/18
to jan.o...@gmail.com, equalsv...@googlegroups.com
On Fri, Nov 9, 2018 at 5:52 AM Jan Ouwens <jan.o...@gmail.com> wrote:
Hi Caleb,

That's a lot of code and not a lot of context ... am I understanding correctly that you have JPA class where equals is based on the id, except when it's new, in which case you look at all the fields? If so, please take a look here: https://jqno.nl/equalsverifier/manual/jpa-entities/

In any event, you shouldn't make two separate, slightly different EqualsVerifier tests for the same class. 

well the `id` test works fine... but I did look at that link prior to posting, but it doesn't really cover how to do what you just said, compare the fields if new.

--

Jan Ouwens

unread,
Nov 9, 2018, 9:24:14 AM11/9/18
to Caleb Cushing, equalsv...@googlegroups.com
It's in the section "IDs and new objects".
You can suppress Warning.IDENTICAL_COPY_FOR_VERSIONED_ENTITY, and (probably) skip the withOnlyTheseFields/withIgnoredFields, that should do the trick.

Caleb Cushing

unread,
Nov 9, 2018, 9:27:08 AM11/9/18
to jan.o...@gmail.com, equalsv...@googlegroups.com
Got it. maybe it would be good to expand these examples a bit to show more comparison when you have multiple property fields. Reading it, I found it a bit confusing on what I should do in this scenario (also never got the error for IDENTICAL_COPY). will report back in a few.
Reply all
Reply to author
Forward
0 new messages