diff --git a/src/main/java/com/java/pojo/internal/assertion/getter/GetterAssertionError.java b/src/main/java/com/java/pojo/internal/assertion/getter/GetterAssertionError.java index 4550e04..f1bdc91 100644 --- a/src/main/java/com/java/pojo/internal/assertion/getter/GetterAssertionError.java +++ b/src/main/java/com/java/pojo/internal/assertion/getter/GetterAssertionError.java @@ -3,7 +3,7 @@ import java.lang.reflect.Field; import com.java.pojo.internal.assertion.AbstractAssertionError; -class GetterAssertionError extends AbstractAssertionError { +public class GetterAssertionError extends AbstractAssertionError { private static final String CONSTRAINT_GETTER = "The getter method for field '%s' should return field value.\n" + "Current implementation returns different value.\n" diff --git a/src/main/java/com/java/pojo/internal/instantiator/ObjectGenerator.java b/src/main/java/com/java/pojo/internal/instantiator/ObjectGenerator.java index 35e4889..0c8c96e 100644 --- a/src/main/java/com/java/pojo/internal/instantiator/ObjectGenerator.java +++ b/src/main/java/com/java/pojo/internal/instantiator/ObjectGenerator.java @@ -52,6 +52,30 @@ public Object generateSameInstance(final Object object) { return newInstance; } + /** + * Creates a new instance of {@code clazz} that is guaranteed to be unique relative to other + * instances created for the same type by applying {@code increaseValue} {@code uniquenessIndex} + * times. This prevents false positives in getter tests when a POJO has multiple fields of the + * same type (e.g. two {@code String} fields). + */ + public Object createUniqueInstance(final Class clazz, final int uniquenessIndex) { + Object value = createNewInstance(clazz); + for (int i = 0; i < uniquenessIndex && value != null; i++) { + try { + final Object increased = abstractFieldValueChanger.increaseValue(value); + if (increased == null) { + break; + } + value = increased; + } catch (final ClassCastException e) { + // A value changer mis-matched this type (pre-existing canChange bug); + // stop increasing and use the value we have. + break; + } + } + return value; + } + public List generateDifferentObjects(final ClassAndFieldPredicatePair baseClassAndFieldPredicatePair, final ClassAndFieldPredicatePair... classAndFieldPredicatePairs) { return generateDifferentObjects(0, diff --git a/src/main/java/com/java/pojo/internal/tester/GetterTester.java b/src/main/java/com/java/pojo/internal/tester/GetterTester.java index 02fc7ad..81813b4 100644 --- a/src/main/java/com/java/pojo/internal/tester/GetterTester.java +++ b/src/main/java/com/java/pojo/internal/tester/GetterTester.java @@ -1,6 +1,5 @@ package com.java.pojo.internal.tester; - import com.java.pojo.api.ClassAndFieldPredicatePair; import com.java.pojo.internal.field.AbstractFieldValueChanger; import com.java.pojo.internal.utils.FieldUtils; @@ -10,6 +9,7 @@ import java.lang.reflect.Method; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.IntStream; public class GetterTester extends AbstractTester { @@ -30,12 +30,17 @@ public void test(final ClassAndFieldPredicatePair baseClassAndFieldPredicatePair final List getterAndFieldPairs = findGettersForFields(testedClass, fields); final Object instance = objectGenerator.createNewInstance(testedClass); - getterAndFieldPairs.forEach(eachPair -> testGetter(eachPair, instance)); + IntStream.range(0, getterAndFieldPairs.size()).forEach(i -> testGetter(getterAndFieldPairs.get(i), instance, i)); } - private void testGetter(final GetterAndFieldPair eachPair, final Object instance) { + private void testGetter(final GetterAndFieldPair eachPair, final Object instance, final int fieldIndex)) { final Method getter = eachPair.getGetter(); final Field field = eachPair.getField(); + // Set a unique value for this field before invoking the getter. Using fieldIndex to + // differentiate fields of the same type (e.g. two String fields), which prevents false + // positives caused by copy-paste getter errors returning the wrong field. + final Object uniqueValue = objectGenerator.createUniqueInstance(field.getType(), fieldIndex); + FieldUtils.setValue(instance, field, uniqueValue); testAssertions.assertThatGetMethodFor(instance) .willGetValueFromField(getter, field); } diff --git a/src/test/java/com/java/pojo/internal/tester/GetterTesterTest.java b/src/test/java/com/java/pojo/internal/tester/GetterTesterTest.java index de0736a..7cfa4ce 100644 --- a/src/test/java/com/java/pojo/internal/tester/GetterTesterTest.java +++ b/src/test/java/com/java/pojo/internal/tester/GetterTesterTest.java @@ -5,6 +5,7 @@ import com.java.pojo.api.FieldPredicate; import com.java.pojo.internal.field.DefaultFieldValueChanger; import com.java.pojo.internal.utils.GetterNotFoundException; +import com.java.pojo.internal.assertion.getter.GetterAssertionError; import java.util.List; @@ -56,6 +57,18 @@ void Should_Pass_All_Getter_Tests_Including_Fields() { assertThat(result).isNull(); } + @Test + void Should_Fail_When_Getter_Returns_Wrong_Field_Of_Same_Type() { + // given - a POJO with a copy-paste getter bug: getFieldB() returns fieldA + final GetterTester getterTester = new GetterTester(DefaultFieldValueChanger.INSTANCE); + + // when + final Throwable result = catchThrowable(() -> getterTester.testAll(CopyPasteGetterBugPojo.class)); + + // then - the tester must detect the wrong getter + assertThat(result).isInstanceOf(GetterAssertionError.class); + } + @Test void Should_Fail_Multiple_Classes() { // given