From 23aadf8c7ea33176af466eb4a69d72fb2c60d576 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Thu, 20 Feb 2025 09:21:04 +0100 Subject: [PATCH 1/4] add ability to use validation checker with classes and inheritance even without an allArgsConstructor; add ability to register validation rules with an registrar function that can be re-used --- .../validation/core/BaseRuleBuilder.java | 6 ++ .../validation/core/RuleValidator.java | 67 ++++++++++++++----- .../validation/ValidationAssertTest.java | 36 ++++++++++ 3 files changed, 94 insertions(+), 15 deletions(-) diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java index fda5fed..58f5326 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; @RequiredArgsConstructor public abstract class BaseRuleBuilder> implements @@ -53,6 +54,11 @@ public void addRule(@NonNull Rule rule) { rules.add(rule); } + public BaseRuleBuilder withAdditionalRules(Consumer> registrar) { + registrar.accept(this); + return this; + } + public void isCompliant() { triggerValidation.run(); } diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java index 1d51f77..9a4a3da 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java @@ -11,6 +11,8 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -217,10 +219,10 @@ private static

Stream getValues(Rule rule, P parameterUnderTest) { private static T createCopyWithAlteredProperty(T original, String property, Object alteredValue) { try { // Get the class of the original object - var clazz = original.getClass(); + var clazz = (Class) original.getClass(); // Get all the declared fields of the class - var fields = clazz.getDeclaredFields(); + var fields = getAllFields(clazz); // Create an array to hold the values of the original object's properties var propertyValues = new Object[fields.length]; @@ -235,10 +237,6 @@ private static T createCopyWithAlteredProperty(T original, String property, parameterTypes[i] = fields[i].getType(); } - // Get the constructor that accepts all properties as arguments - var constructor = clazz.getDeclaredConstructor(parameterTypes); - constructor.setAccessible(true); - // Create an array to hold the new property values var newPropertyValues = new Object[propertyValues.length]; @@ -261,11 +259,10 @@ private static T createCopyWithAlteredProperty(T original, String property, } } - // Create a copy with the altered property value - return (T) constructor.newInstance(newPropertyValues); + return createCopyWithAlteredValues(clazz, parameterTypes, newPropertyValues, fields); } catch (NoSuchMethodException e) { throw new RuleValidationException( - "Error creating copy with altered property. Maybe there is no all-args-constructor?", + "Error creating copy with altered property. Maybe there is no eligible-constructor?", e ); } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { @@ -273,6 +270,35 @@ private static T createCopyWithAlteredProperty(T original, String property, } } + private static T createCopyWithAlteredValues( + Class clazz, + Class[] parameterTypes, + Object[] newPropertyValues, + Field[] fields + ) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { + T instance = null; + try { + // Get the constructor that accepts all properties as arguments + var constructor = clazz.getDeclaredConstructor(parameterTypes); + constructor.setAccessible(true); + + // Create a copy with the altered property value + instance = constructor.newInstance(newPropertyValues); + } catch (NoSuchMethodException e) { + // Get the no args constructor + var constructor = clazz.getDeclaredConstructor(); + constructor.setAccessible(true); + + // Create a copy with the altered property value + instance = constructor.newInstance(); + for (var i = 0; i < newPropertyValues.length; i++) { + fields[i].setAccessible(true); + fields[i].set(instance, newPropertyValues[i]); + } + } + return instance; + } + private static Set getAllPropertiesOf(T object) { var clazz = object.getClass(); @@ -307,12 +333,23 @@ private static boolean hasNullableAnnotation(String propertyName, Object object) private static Field getFieldOrFail(String propertyName, Object object) { var clazz = object.getClass(); - Field field = null; - try { - field = clazz.getDeclaredField(propertyName); - } catch (NoSuchFieldException e) { - throw new RuleValidationException("Property does not exist: " + propertyName, e); + var field = Arrays.stream(getAllFields(clazz)) + .filter( + f -> f.getName().equals(propertyName) + ) + .findFirst(); + + return field.orElseThrow(() -> new RuleValidationException("Property does not exist: " + propertyName)); + } + + private static Field[] getAllFields(Class initialClazz) { + Class clazz = initialClazz; + + var fields = new ArrayList(); + while (clazz != null) { + fields.addAll(Arrays.asList(clazz.getDeclaredFields())); + clazz = clazz.getSuperclass(); } - return field; + return fields.toArray(new Field[0]); } } diff --git a/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java b/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java index 870e944..3a2d8b6 100644 --- a/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java +++ b/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java @@ -616,6 +616,31 @@ void givenNotValidatedClass_shouldAlwaysFail() { ).isEnabled() ); } + + @Test + void shouldAlsoWorkForExtendedClassesEvenWithoutAllArgsConstructors() { + var item = new SomeExtendingClass(); + item.notNull = "notNull"; + item.notNullPositiveOrZero = ScaledBigDecimal.ONE; + + assertThatValidation().of(item) + .usingBeanValidation() + .notNull("notNull") + .notNull("notNull") + .positiveOrZero("notNullPositiveOrZero") + .isCompliant(); + + var invalidItem = new SomeExtendingClass(); + + assertThatExceptionOfType(AssertionError.class).isThrownBy( + () -> assertThatValidation().of(invalidItem) + .usingBeanValidation() + .notNull("notNull") + .notNull("notNull") + .positiveOrZero("notNullPositiveOrZero") + .isCompliant() + ); + } } private static SomeValidParameter getSomeValidParameter() { @@ -791,4 +816,15 @@ public void someMethodWithoutValidParameter(Long first, String last) { public void someMethodWithoutValidParameter(Long first, Integer second, String last) { } } + + public abstract static class SomeBaseClass { + @NotNull + protected String notNull; + } + + public static class SomeExtendingClass extends SomeBaseClass { + @NotNull + @PositiveOrZero + private ScaledBigDecimal notNullPositiveOrZero; + } } From 189ec21a3ecd6a36f485be886604bcd684cfb384 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Thu, 20 Feb 2025 10:04:21 +0100 Subject: [PATCH 2/4] fix typing --- .../validation/core/BaseRuleBuilder.java | 9 +++++---- .../validation/ValidationAssertTest.java | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java index 58f5326..1a7460a 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java @@ -26,7 +26,7 @@ import java.util.function.Consumer; @RequiredArgsConstructor -public abstract class BaseRuleBuilder> implements +public abstract class BaseRuleBuilder> implements ValidationRulesData, BetweenRule, FutureRule, @@ -54,9 +54,10 @@ public void addRule(@NonNull Rule rule) { rules.add(rule); } - public BaseRuleBuilder withAdditionalRules(Consumer> registrar) { - registrar.accept(this); - return this; + public > T withAdditionalRules(Consumer registrar) { + var self = (T) this; + registrar.accept(self); + return self; } public void isCompliant() { diff --git a/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java b/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java index 3a2d8b6..c62a9c6 100644 --- a/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java +++ b/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java @@ -32,6 +32,7 @@ import java.time.YearMonth; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; +import java.util.function.Consumer; import static it.aboutbits.springboot.testing.validation.ValidationAssertTest.TestValidationAssert.assertThatValidation; import static org.assertj.core.api.Assertions.assertThatCode; @@ -641,6 +642,24 @@ void shouldAlsoWorkForExtendedClassesEvenWithoutAllArgsConstructors() { .isCompliant() ); } + + @Test + void usingRuleRegistrarShouldWork() { + var item = new SomeExtendingClass(); + item.notNull = "notNull"; + item.notNullPositiveOrZero = ScaledBigDecimal.ONE; + + Consumer registrar = (ruleBuilder -> ruleBuilder + .notNull("notNull") + .notNull("notNull") + .positiveOrZero("notNullPositiveOrZero") + ); + + assertThatValidation().of(item) + .usingBeanValidation() + .withAdditionalRules(registrar) + .isCompliant(); + } } private static SomeValidParameter getSomeValidParameter() { From dcfd8a4c60100c9d7b4df939e9ad990a5029a9c7 Mon Sep 17 00:00:00 2001 From: Andreas Hufler Date: Thu, 20 Feb 2025 10:40:44 +0100 Subject: [PATCH 3/4] move tests --- .../validation/ValidationAssertTest.java | 75 ++++++++++--------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java b/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java index c62a9c6..3bb826b 100644 --- a/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java +++ b/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java @@ -617,51 +617,52 @@ void givenNotValidatedClass_shouldAlwaysFail() { ).isEnabled() ); } + } - @Test - void shouldAlsoWorkForExtendedClassesEvenWithoutAllArgsConstructors() { - var item = new SomeExtendingClass(); - item.notNull = "notNull"; - item.notNullPositiveOrZero = ScaledBigDecimal.ONE; + @Test + void shouldAlsoWorkForExtendedClassesEvenWithoutAllArgsConstructors() { + var item = new SomeExtendingClass(); + item.notNull = "notNull"; + item.notNullPositiveOrZero = ScaledBigDecimal.ONE; - assertThatValidation().of(item) - .usingBeanValidation() - .notNull("notNull") - .notNull("notNull") - .positiveOrZero("notNullPositiveOrZero") - .isCompliant(); + assertThatValidation().of(item) + .usingBeanValidation() + .notNull("notNull") + .notNull("notNull") + .positiveOrZero("notNullPositiveOrZero") + .isCompliant(); - var invalidItem = new SomeExtendingClass(); + var invalidItem = new SomeExtendingClass(); - assertThatExceptionOfType(AssertionError.class).isThrownBy( - () -> assertThatValidation().of(invalidItem) - .usingBeanValidation() - .notNull("notNull") - .notNull("notNull") - .positiveOrZero("notNullPositiveOrZero") - .isCompliant() - ); - } + assertThatExceptionOfType(AssertionError.class).isThrownBy( + () -> assertThatValidation().of(invalidItem) + .usingBeanValidation() + .notNull("notNull") + .notNull("notNull") + .positiveOrZero("notNullPositiveOrZero") + .isCompliant() + ); + } - @Test - void usingRuleRegistrarShouldWork() { - var item = new SomeExtendingClass(); - item.notNull = "notNull"; - item.notNullPositiveOrZero = ScaledBigDecimal.ONE; - - Consumer registrar = (ruleBuilder -> ruleBuilder - .notNull("notNull") - .notNull("notNull") - .positiveOrZero("notNullPositiveOrZero") - ); + @Test + void usingRuleRegistrarShouldWork() { + var item = new SomeExtendingClass(); + item.notNull = "notNull"; + item.notNullPositiveOrZero = ScaledBigDecimal.ONE; + + Consumer registrar = (ruleBuilder -> ruleBuilder + .notNull("notNull") + .notNull("notNull") + .positiveOrZero("notNullPositiveOrZero") + ); - assertThatValidation().of(item) - .usingBeanValidation() - .withAdditionalRules(registrar) - .isCompliant(); - } + assertThatValidation().of(item) + .usingBeanValidation() + .withAdditionalRules(registrar) + .isCompliant(); } + private static SomeValidParameter getSomeValidParameter() { return new SomeValidParameter( // NotNull From b0e5e3068e4acbfddad8af8c3be973da3f208e17 Mon Sep 17 00:00:00 2001 From: SirCotare Date: Thu, 20 Feb 2025 11:47:40 +0100 Subject: [PATCH 4/4] Update src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java Co-authored-by: Thomas Sapelza --- .../springboot/testing/validation/ValidationAssertTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java b/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java index 3bb826b..a210553 100644 --- a/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java +++ b/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java @@ -650,11 +650,10 @@ void usingRuleRegistrarShouldWork() { item.notNull = "notNull"; item.notNullPositiveOrZero = ScaledBigDecimal.ONE; - Consumer registrar = (ruleBuilder -> ruleBuilder + var registrar = (Consumer) ruleBuilder -> ruleBuilder .notNull("notNull") .notNull("notNull") - .positiveOrZero("notNullPositiveOrZero") - ); + .positiveOrZero("notNullPositiveOrZero"); assertThatValidation().of(item) .usingBeanValidation()