meanbeanlib / meanbean

Automated JavaBean Testing
Apache License 2.0
15 stars 3 forks source link

HashCodeMethodTester doesn't expose configuration like BeanTester #21

Closed nkavian closed 2 years ago

nkavian commented 2 years ago

When using BeanTester, I can provide a configuration that overrides a factory for a field and then pass that configuration in.

beanTester.testBean(target, getConfiguration());

When using HashCodeMethodTester I don't have that option, and I get the output:

Using dynamically created factory for [{}] of type [{}] because a custom Factory cannot be found.

Desired outcome: provide a way to pass in Configuration into HashCodeMethodTester.

maxxedev commented 2 years ago

would this work:

public class Foo {
    private Bar bar;
    // getters/setters, equals/hashcode
}

public class Bar {
    public Bar(Object x) {

    }
}

@Test
public void test() {
    BeanVerifier.forClass(Foo.class)
            .withSettings(settings -> 
                    settings.registerFactory(Bar.class, () -> new Bar(RandomValueGenerator.getInstance().nextLong())))
            .verifyEqualsAndHashCode();
}
nkavian commented 2 years ago

Thanks for sharing the example. Looks like I'm using the v2 code inside 3.0.0-M9 and your example is v3 code.

When using v3 verifyEqualsAndHashCode with v2 beanTester.testBean, there's an NPE in the library. There's also a behavior difference between v3 verifyEqualsAndHashCode and HashCodeMethodTester.

These issues are mostly on my side since I could just upgrade to v3. Closing the ticket.

maxxedev commented 2 years ago

When using v3 verifyEqualsAndHashCode with v2 beanTester.testBean, there's an NPE in the library. There's also a behavior difference between v3 verifyEqualsAndHashCode and HashCodeMethodTester.

Curious about NPE and behavior differences

nkavian commented 2 years ago

Here is pseudo code to try to explain the scenarios.

A) Testing getters and setters

configuration = new ConfigurationBuilder().overrideFactory("onboarding", () -> new Onboarding(63)).build();
new BeanTester().testBean(target, configuration);

B) Testing hash code

new HashCodeMethodTester().testHashCodeMethod(target);

Initially, I was using Lombok @Data. Meanbean was then implemented. Then some time later, we decided equality of our models are better suited if we just compare a primaryId of each model file. i.e. public int hashCode() { return Objects.hashCode(primaryId()); }. Note, (B) still works with this design.

Running A+B works fine.

C) Using your suggestion to use verifyEqualsAndHashCode in order to access the settings:

BeanVerifier.forClass(target)
    .withSettings(settings -> settings.registerFactory(Onboarding.class, () -> new Onboarding(63)))
    .verifyEqualsAndHashCode();

C.1) Run just this unit test method and I get Trace 1. I refer to this as the behavior change (when compared to (B)). The message is correct I think, If I have a custom hashCode, then maybe I shouldn't test it with meanbean.

C.2) Run A+C (i.e. run the whole unit test class) and I get Trace 2. I refer to this as the NPE. I don't think it's on my side since all I did was switch B to C / A was untouched.

Trace 1

FAILED: testHashCode
java.lang.AssertionError: objects that differ due to supposedly significant property [enableRamp] were considered equal. (
x.enableRamp=[true]
vs
y.enableRamp=[false]
). is property [enableRamp] actually insignificant?
    at org.meanbean.util.AssertionUtils.fail(AssertionUtils.java:51)
    at org.meanbean.test.SignificantObjectPropertyEqualityConsistentAsserter.assertConsistent(SignificantObjectPropertyEqualityConsistentAsserter.java:99)
    at org.meanbean.test.PropertyBasedEqualsMethodPropertySignificanceVerifier.verifyEqualsMethodForProperty(PropertyBasedEqualsMethodPropertySignificanceVerifier.java:377)
    at org.meanbean.test.PropertyBasedEqualsMethodPropertySignificanceVerifier.verifyEqualsMethod(PropertyBasedEqualsMethodPropertySignificanceVerifier.java:266)
    at org.meanbean.test.EqualsMethodTester.testEqualsMethod(EqualsMethodTester.java:447)
    at org.meanbean.test.EqualsMethodTester.testEqualsMethod(EqualsMethodTester.java:298)
    at org.meanbean.test.EqualsMethodTester.testEqualsMethod(EqualsMethodTester.java:232)
    at org.meanbean.test.BeanVerifierImpl.verifyEqualsAndHashCode(BeanVerifierImpl.java:70)

Trace 2

FAILED: testGettersAndSetters
java.lang.NullPointerException: context key not available
    at java.util.Objects.requireNonNull(Objects.java:228)
    at org.meanbean.util.ServiceFactory$ServiceContextMap.getContextMap(ServiceFactory.java:134)
    at org.meanbean.util.ServiceFactory.getInstance(ServiceFactory.java:47)
    at org.meanbean.util.ServiceDefinition.getServiceFactory(ServiceDefinition.java:60)
    at org.meanbean.factories.FactoryCollection.getInstance(FactoryCollection.java:68)
    at org.meanbean.factories.BasicNewObjectInstanceFactory.findBeanFactory(BasicNewObjectInstanceFactory.java:43)
    at org.meanbean.test.BeanTester.testBean(BeanTester.java:342)
    at org.meanbean.test.BeanTester.testBean(BeanTester.java:297)
maxxedev commented 2 years ago

@nkavian

I am unable to reproduce NPE with the following code. Would you be able to share more details on how to reproduce? Thanks

package example;

import org.junit.Test;
import org.meanbean.test.BeanTester;
import org.meanbean.test.BeanVerifier;
import org.meanbean.test.Configuration;
import org.meanbean.test.ConfigurationBuilder;
import org.meanbean.test.HashCodeMethodTester;

public class MyTest {

    public static class Onboarding {
        private int id;
        private boolean enableRamp;

        public Onboarding() {
        }

        public Onboarding(int id) {
            this.id = id;
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public boolean isEnableRamp() {
            return enableRamp;
        }

        public void setEnableRamp(boolean enableRamp) {
            this.enableRamp = enableRamp;
        }

        @Override
        public int hashCode() {
            return id;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof Onboarding)) {
                return false;
            }
            Onboarding that = (Onboarding) obj;
            return that.id == this.id;
        }
    }

    private final Class<?> target = Onboarding.class;

    @Test
    public void testA_gettersAndSetters() {
        Configuration configuration = new ConfigurationBuilder()
                .overrideFactory("onboarding", () -> new Onboarding(63))
                .build();
        new BeanTester().testBean(target, configuration);
    }

    @Test
    public void testB_hashCode() {
        new HashCodeMethodTester().testHashCodeMethod(target);
    }

    @Test
    public void testC_hashCode() {
        BeanVerifier.forClass(target)
                .withSettings(settings -> settings.registerFactory(Onboarding.class, () -> new Onboarding(63)))
                .verifyEqualsAndHashCode();
    }
}