Skip to content

Commit 23aadf8

Browse files
committed
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
1 parent f78d5cf commit 23aadf8

3 files changed

Lines changed: 94 additions & 15 deletions

File tree

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

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

2728
@RequiredArgsConstructor
2829
public abstract class BaseRuleBuilder<R extends BaseRuleBuilder<?>> implements
@@ -53,6 +54,11 @@ public void addRule(@NonNull Rule rule) {
5354
rules.add(rule);
5455
}
5556

57+
public BaseRuleBuilder<R> withAdditionalRules(Consumer<BaseRuleBuilder<R>> registrar) {
58+
registrar.accept(this);
59+
return this;
60+
}
61+
5662
public void isCompliant() {
5763
triggerValidation.run();
5864
}

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: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,31 @@ void givenNotValidatedClass_shouldAlwaysFail() {
616616
).isEnabled()
617617
);
618618
}
619+
620+
@Test
621+
void shouldAlsoWorkForExtendedClassesEvenWithoutAllArgsConstructors() {
622+
var item = new SomeExtendingClass();
623+
item.notNull = "notNull";
624+
item.notNullPositiveOrZero = ScaledBigDecimal.ONE;
625+
626+
assertThatValidation().of(item)
627+
.usingBeanValidation()
628+
.notNull("notNull")
629+
.notNull("notNull")
630+
.positiveOrZero("notNullPositiveOrZero")
631+
.isCompliant();
632+
633+
var invalidItem = new SomeExtendingClass();
634+
635+
assertThatExceptionOfType(AssertionError.class).isThrownBy(
636+
() -> assertThatValidation().of(invalidItem)
637+
.usingBeanValidation()
638+
.notNull("notNull")
639+
.notNull("notNull")
640+
.positiveOrZero("notNullPositiveOrZero")
641+
.isCompliant()
642+
);
643+
}
619644
}
620645

621646
private static SomeValidParameter getSomeValidParameter() {
@@ -791,4 +816,15 @@ public void someMethodWithoutValidParameter(Long first, String last) {
791816
public void someMethodWithoutValidParameter(Long first, Integer second, String last) {
792817
}
793818
}
819+
820+
public abstract static class SomeBaseClass {
821+
@NotNull
822+
protected String notNull;
823+
}
824+
825+
public static class SomeExtendingClass extends SomeBaseClass {
826+
@NotNull
827+
@PositiveOrZero
828+
private ScaledBigDecimal notNullPositiveOrZero;
829+
}
794830
}

0 commit comments

Comments
 (0)