diff --git a/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java index 5817814..08cb915 100644 --- a/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java +++ b/src/main/java/it/aboutbits/springboot/testing/validation/core/BaseValidationAssert.java @@ -1,18 +1,23 @@ package it.aboutbits.springboot.testing.validation.core; import it.aboutbits.springboot.toolbox.type.CustomType; +import jakarta.validation.Valid; import lombok.AccessLevel; import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.Setter; import org.apache.logging.log4j.util.TriConsumer; +import org.springframework.validation.annotation.Validated; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; +import static org.assertj.core.api.Assertions.assertThat; + @RequiredArgsConstructor(access = AccessLevel.PROTECTED) public abstract class BaseValidationAssert> { @Getter(AccessLevel.PROTECTED) @@ -46,6 +51,43 @@ public

CallBuilder of(@NonNull P parameterUnderTest) { return new CallBuilder<>(this); } + @SuppressWarnings("unused") + public AnnotationChecker calling( + @NonNull Class classUnderTest, + @NonNull String methodName, + @NonNull Class... methodParameterTypes + ) { + return new AnnotationChecker(classUnderTest, methodName, methodParameterTypes); + } + + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + public static final class AnnotationChecker { + private final Class classUnderTest; + private final String methodName; + private final Class[] methodParameterTypes; + + public void isEnabled() { + assertThat(classUnderTest.isAnnotationPresent(Validated.class)).isTrue(); + + try { + var method = classUnderTest.getMethod(methodName, methodParameterTypes); + var parameter = method.getParameters()[method.getParameterCount() - 1]; + assertThat(parameter.isAnnotationPresent(Valid.class)).isTrue(); + } catch (NoSuchMethodException e) { + throw new AssertionError( + "Method \"%s(%s)\" not found in class \"%s\"".formatted( + methodName, + String.join( + ", ", + Arrays.stream(methodParameterTypes).map(Class::getCanonicalName).toList() + ), + classUnderTest.getCanonicalName() + ), e + ); + } + } + } + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public static final class CallBuilder, P> { private final BaseValidationAssert parent; 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 4fbfef3..870e944 100644 --- a/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java +++ b/src/test/java/it/aboutbits/springboot/testing/validation/ValidationAssertTest.java @@ -15,8 +15,10 @@ import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.PositiveOrZero; import lombok.With; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.lang.Nullable; +import org.springframework.validation.annotation.Validated; import java.math.BigDecimal; import java.math.BigInteger; @@ -32,6 +34,7 @@ import java.time.temporal.ChronoUnit; import static it.aboutbits.springboot.testing.validation.ValidationAssertTest.TestValidationAssert.assertThatValidation; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; class ValidationAssertTest { @@ -524,6 +527,97 @@ void propertyMissingRule_shouldFail() { .isCompliant()); } + @Nested + @SuppressWarnings("java:S5778") // Suppress the warning for lambdas with more than one exception cause + class AnnotationChecking { + @Test + void givenValidatedClass_andValidParameter_shouldSucceed() { + assertThatCode( + () -> assertThatValidation().calling( + ClassWithValidated.class, + "someMethodWithValidParameter", + String.class + ).isEnabled() + ).doesNotThrowAnyException(); + + assertThatCode( + () -> assertThatValidation().calling( + ClassWithValidated.class, + "someMethodWithValidParameter", + Long.class, + String.class + ).isEnabled() + ).doesNotThrowAnyException(); + + assertThatCode( + () -> assertThatValidation().calling( + ClassWithValidated.class, + "someMethodWithValidParameter", + Long.class, + Integer.class, + String.class + ).isEnabled() + ).doesNotThrowAnyException(); + } + + @Test + void givenNotValidatedClass_shouldAlwaysFail() { + assertThatExceptionOfType(AssertionError.class).isThrownBy( + () -> assertThatValidation().calling( + ClassWithoutValidated.class, + "someMethodWithValidParameter", + String.class + ).isEnabled() + ); + + assertThatExceptionOfType(AssertionError.class).isThrownBy( + () -> assertThatValidation().calling( + ClassWithoutValidated.class, + "someMethodWithValidParameter", + Long.class, + String.class + ).isEnabled() + ); + + assertThatExceptionOfType(AssertionError.class).isThrownBy( + () -> assertThatValidation().calling( + ClassWithoutValidated.class, + "someMethodWithValidParameter", + Long.class, + Integer.class, + String.class + ).isEnabled() + ); + + assertThatExceptionOfType(AssertionError.class).isThrownBy( + () -> assertThatValidation().calling( + ClassWithoutValidated.class, + "someMethodWithoutValidParameter", + String.class + ).isEnabled() + ); + + assertThatExceptionOfType(AssertionError.class).isThrownBy( + () -> assertThatValidation().calling( + ClassWithoutValidated.class, + "someMethodWithoutValidParameter", + Long.class, + String.class + ).isEnabled() + ); + + assertThatExceptionOfType(AssertionError.class).isThrownBy( + () -> assertThatValidation().calling( + ClassWithoutValidated.class, + "someMethodWithoutValidParameter", + Long.class, + Integer.class, + String.class + ).isEnabled() + ); + } + } + private static SomeValidParameter getSomeValidParameter() { return new SomeValidParameter( // NotNull @@ -654,4 +748,47 @@ public static TestValidationAssert assertThatValidation() { public static final class TestRuleBuilder extends BaseRuleBuilder { } } + + @SuppressWarnings({"java:S1186", "unused"}) // Suppress the "empty method body" warning + @Validated + public static class ClassWithValidated { + public void someMethodWithValidParameter(@Valid String last) { + } + + public void someMethodWithValidParameter(Long first, @Valid String last) { + } + + public void someMethodWithValidParameter(Long first, Integer second, @Valid String last) { + } + + public void someMethodWithoutValidParameter(String last) { + } + + public void someMethodWithoutValidParameter(Long first, String last) { + } + + public void someMethodWithoutValidParameter(Long first, Integer second, String last) { + } + } + + @SuppressWarnings({"java:S1186", "unused"}) // Suppress the "empty method body" warning + public static class ClassWithoutValidated { + public void someMethodWithValidParameter(@Valid String last) { + } + + public void someMethodWithValidParameter(Long first, @Valid String last) { + } + + public void someMethodWithValidParameter(Long first, Integer second, @Valid String last) { + } + + public void someMethodWithoutValidParameter(String last) { + } + + public void someMethodWithoutValidParameter(Long first, String last) { + } + + public void someMethodWithoutValidParameter(Long first, Integer second, String last) { + } + } }