Skip to content

Commit f57ea69

Browse files
authored
Merge pull request #14 from aboutbits/validation_checking
add ability to use validation checker with classes and inheritance
2 parents f78d5cf + b0e5e30 commit f57ea69

3 files changed

Lines changed: 115 additions & 16 deletions

File tree

src/main/java/it/aboutbits/springboot/testing/validation/core/BaseRuleBuilder.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@
2323

2424
import java.util.ArrayList;
2525
import java.util.List;
26+
import java.util.function.Consumer;
2627

2728
@RequiredArgsConstructor
28-
public abstract class BaseRuleBuilder<R extends BaseRuleBuilder<?>> implements
29+
public abstract class BaseRuleBuilder<R extends BaseRuleBuilder<R>> implements
2930
ValidationRulesData,
3031
BetweenRule<R>,
3132
FutureRule<R>,
@@ -53,6 +54,12 @@ public void addRule(@NonNull Rule rule) {
5354
rules.add(rule);
5455
}
5556

57+
public <T extends BaseRuleBuilder<T>> T withAdditionalRules(Consumer<T> registrar) {
58+
var self = (T) this;
59+
registrar.accept(self);
60+
return self;
61+
}
62+
5663
public void isCompliant() {
5764
triggerValidation.run();
5865
}

src/main/java/it/aboutbits/springboot/testing/validation/core/RuleValidator.java

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
import java.lang.reflect.Field;
1313
import java.lang.reflect.InvocationTargetException;
14+
import java.util.ArrayList;
15+
import java.util.Arrays;
1416
import java.util.HashSet;
1517
import java.util.List;
1618
import java.util.Set;
@@ -217,10 +219,10 @@ private static <P> Stream<?> getValues(Rule rule, P parameterUnderTest) {
217219
private static <T> T createCopyWithAlteredProperty(T original, String property, Object alteredValue) {
218220
try {
219221
// Get the class of the original object
220-
var clazz = original.getClass();
222+
var clazz = (Class<T>) original.getClass();
221223

222224
// Get all the declared fields of the class
223-
var fields = clazz.getDeclaredFields();
225+
var fields = getAllFields(clazz);
224226

225227
// Create an array to hold the values of the original object's properties
226228
var propertyValues = new Object[fields.length];
@@ -235,10 +237,6 @@ private static <T> T createCopyWithAlteredProperty(T original, String property,
235237
parameterTypes[i] = fields[i].getType();
236238
}
237239

238-
// Get the constructor that accepts all properties as arguments
239-
var constructor = clazz.getDeclaredConstructor(parameterTypes);
240-
constructor.setAccessible(true);
241-
242240
// Create an array to hold the new property values
243241
var newPropertyValues = new Object[propertyValues.length];
244242

@@ -261,18 +259,46 @@ private static <T> T createCopyWithAlteredProperty(T original, String property,
261259
}
262260
}
263261

264-
// Create a copy with the altered property value
265-
return (T) constructor.newInstance(newPropertyValues);
262+
return createCopyWithAlteredValues(clazz, parameterTypes, newPropertyValues, fields);
266263
} catch (NoSuchMethodException e) {
267264
throw new RuleValidationException(
268-
"Error creating copy with altered property. Maybe there is no all-args-constructor?",
265+
"Error creating copy with altered property. Maybe there is no eligible-constructor?",
269266
e
270267
);
271268
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
272269
throw new RuleValidationException("Error creating copy with altered property: " + property, e);
273270
}
274271
}
275272

273+
private static <T> T createCopyWithAlteredValues(
274+
Class<T> clazz,
275+
Class<?>[] parameterTypes,
276+
Object[] newPropertyValues,
277+
Field[] fields
278+
) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
279+
T instance = null;
280+
try {
281+
// Get the constructor that accepts all properties as arguments
282+
var constructor = clazz.getDeclaredConstructor(parameterTypes);
283+
constructor.setAccessible(true);
284+
285+
// Create a copy with the altered property value
286+
instance = constructor.newInstance(newPropertyValues);
287+
} catch (NoSuchMethodException e) {
288+
// Get the no args constructor
289+
var constructor = clazz.getDeclaredConstructor();
290+
constructor.setAccessible(true);
291+
292+
// Create a copy with the altered property value
293+
instance = constructor.newInstance();
294+
for (var i = 0; i < newPropertyValues.length; i++) {
295+
fields[i].setAccessible(true);
296+
fields[i].set(instance, newPropertyValues[i]);
297+
}
298+
}
299+
return instance;
300+
}
301+
276302
private static <T> Set<String> getAllPropertiesOf(T object) {
277303
var clazz = object.getClass();
278304

@@ -307,12 +333,23 @@ private static boolean hasNullableAnnotation(String propertyName, Object object)
307333
private static Field getFieldOrFail(String propertyName, Object object) {
308334
var clazz = object.getClass();
309335

310-
Field field = null;
311-
try {
312-
field = clazz.getDeclaredField(propertyName);
313-
} catch (NoSuchFieldException e) {
314-
throw new RuleValidationException("Property does not exist: " + propertyName, e);
336+
var field = Arrays.stream(getAllFields(clazz))
337+
.filter(
338+
f -> f.getName().equals(propertyName)
339+
)
340+
.findFirst();
341+
342+
return field.orElseThrow(() -> new RuleValidationException("Property does not exist: " + propertyName));
343+
}
344+
345+
private static Field[] getAllFields(Class<?> initialClazz) {
346+
Class<?> clazz = initialClazz;
347+
348+
var fields = new ArrayList<Field>();
349+
while (clazz != null) {
350+
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
351+
clazz = clazz.getSuperclass();
315352
}
316-
return field;
353+
return fields.toArray(new Field[0]);
317354
}
318355
}

src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.time.YearMonth;
3333
import java.time.ZonedDateTime;
3434
import java.time.temporal.ChronoUnit;
35+
import java.util.function.Consumer;
3536

3637
import static it.aboutbits.springboot.testing.validation.ValidationAssertTest.TestValidationAssert.assertThatValidation;
3738
import static org.assertj.core.api.Assertions.assertThatCode;
@@ -618,6 +619,49 @@ void givenNotValidatedClass_shouldAlwaysFail() {
618619
}
619620
}
620621

622+
@Test
623+
void shouldAlsoWorkForExtendedClassesEvenWithoutAllArgsConstructors() {
624+
var item = new SomeExtendingClass();
625+
item.notNull = "notNull";
626+
item.notNullPositiveOrZero = ScaledBigDecimal.ONE;
627+
628+
assertThatValidation().of(item)
629+
.usingBeanValidation()
630+
.notNull("notNull")
631+
.notNull("notNull")
632+
.positiveOrZero("notNullPositiveOrZero")
633+
.isCompliant();
634+
635+
var invalidItem = new SomeExtendingClass();
636+
637+
assertThatExceptionOfType(AssertionError.class).isThrownBy(
638+
() -> assertThatValidation().of(invalidItem)
639+
.usingBeanValidation()
640+
.notNull("notNull")
641+
.notNull("notNull")
642+
.positiveOrZero("notNullPositiveOrZero")
643+
.isCompliant()
644+
);
645+
}
646+
647+
@Test
648+
void usingRuleRegistrarShouldWork() {
649+
var item = new SomeExtendingClass();
650+
item.notNull = "notNull";
651+
item.notNullPositiveOrZero = ScaledBigDecimal.ONE;
652+
653+
var registrar = (Consumer<TestValidationAssert.TestRuleBuilder>) ruleBuilder -> ruleBuilder
654+
.notNull("notNull")
655+
.notNull("notNull")
656+
.positiveOrZero("notNullPositiveOrZero");
657+
658+
assertThatValidation().of(item)
659+
.usingBeanValidation()
660+
.withAdditionalRules(registrar)
661+
.isCompliant();
662+
}
663+
664+
621665
private static SomeValidParameter getSomeValidParameter() {
622666
return new SomeValidParameter(
623667
// NotNull
@@ -791,4 +835,15 @@ public void someMethodWithoutValidParameter(Long first, String last) {
791835
public void someMethodWithoutValidParameter(Long first, Integer second, String last) {
792836
}
793837
}
838+
839+
public abstract static class SomeBaseClass {
840+
@NotNull
841+
protected String notNull;
842+
}
843+
844+
public static class SomeExtendingClass extends SomeBaseClass {
845+
@NotNull
846+
@PositiveOrZero
847+
private ScaledBigDecimal notNullPositiveOrZero;
848+
}
794849
}

0 commit comments

Comments
 (0)