From a9c00b371464048afe8588ec35763fb2eaf9c3ae Mon Sep 17 00:00:00 2001 From: shark300 Date: Tue, 26 Jul 2022 22:53:11 +0200 Subject: [PATCH] HV-1921 Create a tester library for constraint validators with dependency injection --- documentation/pom.xml | 12 +- documentation/src/main/asciidoc/ch06.asciidoc | 55 +++++++++ .../CustomValidatorWithDependencyTest.java | 50 ++++++++ .../customvalidatorwithdependency/Person.java | 13 ++ .../ZipCode.java | 33 +++++ .../ZipCodeRepository.java | 5 + .../ZipCodeValidator.java | 25 ++++ ...econfiguredConstraintValidatorFactory.java | 68 +++++++++++ ...econfiguredValidatorsValidatorFactory.java | 114 ++++++++++++++++++ 9 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/CustomValidatorWithDependencyTest.java create mode 100644 documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/Person.java create mode 100644 documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/ZipCode.java create mode 100644 documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/ZipCodeRepository.java create mode 100644 documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/ZipCodeValidator.java create mode 100644 test-utils/src/main/java/org/hibernate/validator/testutil/PreconfiguredConstraintValidatorFactory.java create mode 100644 test-utils/src/main/java/org/hibernate/validator/testutil/PreconfiguredValidatorsValidatorFactory.java diff --git a/documentation/pom.xml b/documentation/pom.xml index af8c2e61a5..ae335a2ef0 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -39,7 +39,7 @@ true true - -Duser.language=en -Duser.country=US + --add-opens java.base/java.lang=ALL-UNNAMED -Duser.language=en -Duser.country=US forbidden-allow-junit.txt .. @@ -61,6 +61,11 @@ hibernate-validator-cdi test + + ${project.groupId} + hibernate-validator-test-utils + test + jakarta.enterprise jakarta.enterprise.cdi-api @@ -97,6 +102,11 @@ junit test + + org.easymock + easymock + test + org.assertj diff --git a/documentation/src/main/asciidoc/ch06.asciidoc b/documentation/src/main/asciidoc/ch06.asciidoc index 6746906a37..e363a52ac3 100644 --- a/documentation/src/main/asciidoc/ch06.asciidoc +++ b/documentation/src/main/asciidoc/ch06.asciidoc @@ -323,6 +323,61 @@ include::{sourcedir}/org/hibernate/validator/referenceguide/chapter06/CarTest.ja ---- ==== +[[validator-dependency-testing]] +==== Testing constraint validator with dependencies + +Some DI frameworks (e.g. Spring) are capable of injecting dependencies into constraint validator instance: + +[[example-person-with-checkcase]] +.Hibernate Validator test utilities Maven dependency +==== +[source, XML] +[subs="verbatim,attributes"] +---- + + org.hibernate.validator + hibernate-validator-test-utils + {hvVersion} + test + +---- +==== + +.Defining the `@ZipCode` constraint annotation +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/ZipCode.java[tags=include] +---- +==== + +.Applying the `@ZipCode` constraint +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/Person.java[tags=include] +---- +==== + +.Using injected dependency in a constraint validator +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/ZipCodeValidator.java[tags=include] +---- +==== + +Finally, <> demonstrates how validating a `Person` instance which calls custom mocked validator. + +[[example-using-validator-dependency]] +.Validating objects with the `@ZipCode` constraint +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/CustomValidatorWithDependencyTest.java[tags=field] +---- +==== + [[section-class-level-constraints]] === Class-level constraints diff --git a/documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/CustomValidatorWithDependencyTest.java b/documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/CustomValidatorWithDependencyTest.java new file mode 100644 index 0000000000..8efe5fb5fb --- /dev/null +++ b/documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/CustomValidatorWithDependencyTest.java @@ -0,0 +1,50 @@ +package org.hibernate.validator.referenceguide.chapter06.customvalidatorwithdependency; + +import static org.easymock.EasyMock.eq; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.isA; +import static org.easymock.EasyMock.mock; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertEquals; + +import java.util.Map; +import java.util.Set; + +import jakarta.validation.ConstraintValidatorContext; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import org.hibernate.validator.testutil.PreconfiguredValidatorsValidatorFactory; +import org.junit.Test; + +@SuppressWarnings("unused") +//tag::field[] +public class CustomValidatorWithDependencyTest { + + @Test + public void mockCustomValidatorWithDependency() { + ZipCodeValidator zipCodeValidator = mock( ZipCodeValidator.class ); + + expect( zipCodeValidator.isValid( eq( "1234" ), isA( ConstraintValidatorContext.class ) ) ) + .andStubReturn( true ); + zipCodeValidator.initialize( isA( ZipCode.class ) ); + + replay( zipCodeValidator ); + + ValidatorFactory validatorFactory = PreconfiguredValidatorsValidatorFactory.builder() + .defaultValidators( Map.of( ZipCodeValidator.class, zipCodeValidator ) ) + .build(); + + Validator validator = validatorFactory.getValidator(); + + Person person = new Person( "1234" ); + + Set> constraintViolations = validator.validate( person ); + + assertEquals( 0, constraintViolations.size() ); + + verify( zipCodeValidator ); + } +} +//end::field[] diff --git a/documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/Person.java b/documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/Person.java new file mode 100644 index 0000000000..3e6699fe71 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/Person.java @@ -0,0 +1,13 @@ +//tag::include[] +package org.hibernate.validator.referenceguide.chapter06.customvalidatorwithdependency; + +public class Person { + + @ZipCode + private String zipCode; + + public Person(String zipCode) { + this.zipCode = zipCode; + } +} +//end::include[] diff --git a/documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/ZipCode.java b/documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/ZipCode.java new file mode 100644 index 0000000000..eb7923f875 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/ZipCode.java @@ -0,0 +1,33 @@ +//tag::include[] +package org.hibernate.validator.referenceguide.chapter06.customvalidatorwithdependency; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +//end::include[] + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +//tag::include[] +@Target({METHOD, FIELD, ANNOTATION_TYPE, TYPE_USE}) +@Retention(RUNTIME) +@Constraint(validatedBy = ZipCodeValidator.class) +@Documented +public @interface ZipCode { + + String message() default "{org.hibernate.validator.referenceguide.chapter06." + + "customvalidatorwithdependency.ZipCode.message}"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} +//end::include[] diff --git a/documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/ZipCodeRepository.java b/documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/ZipCodeRepository.java new file mode 100644 index 0000000000..82629a8ce8 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/ZipCodeRepository.java @@ -0,0 +1,5 @@ +package org.hibernate.validator.referenceguide.chapter06.customvalidatorwithdependency; + +public interface ZipCodeRepository { + boolean isExist(String zipCode); +} diff --git a/documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/ZipCodeValidator.java b/documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/ZipCodeValidator.java new file mode 100644 index 0000000000..1184f33b1e --- /dev/null +++ b/documentation/src/test/java/org/hibernate/validator/referenceguide/chapter06/customvalidatorwithdependency/ZipCodeValidator.java @@ -0,0 +1,25 @@ +//tag::include[] +package org.hibernate.validator.referenceguide.chapter06.customvalidatorwithdependency; + +//end::include[] + +import jakarta.inject.Inject; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +//tag::include[] +public class ZipCodeValidator implements ConstraintValidator { + + @Inject + public ZipCodeRepository zipCodeRepository; + + @Override + public boolean isValid(String zipCode, ConstraintValidatorContext constraintContext) { + if ( zipCode == null ) { + return true; + } + + return zipCodeRepository.isExist( zipCode ); + } +} +//end::include[] diff --git a/test-utils/src/main/java/org/hibernate/validator/testutil/PreconfiguredConstraintValidatorFactory.java b/test-utils/src/main/java/org/hibernate/validator/testutil/PreconfiguredConstraintValidatorFactory.java new file mode 100644 index 0000000000..b4c562f813 --- /dev/null +++ b/test-utils/src/main/java/org/hibernate/validator/testutil/PreconfiguredConstraintValidatorFactory.java @@ -0,0 +1,68 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.testutil; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorFactory; + +import java.util.HashMap; +import java.util.Map; + +public class PreconfiguredConstraintValidatorFactory implements ConstraintValidatorFactory { + + private final Map, ConstraintValidator> defaultValidators; + private final ConstraintValidatorFactory delegated; + + private PreconfiguredConstraintValidatorFactory(Builder builder) { + this.defaultValidators = builder.defaultValidators; + this.delegated = builder.delegated; + } + + public static Builder builder() { + return new Builder(); + } + + @SuppressWarnings("unchecked") + @Override + public > T getInstance(Class key) { + if ( defaultValidators.containsKey( key ) ) { + return (T) defaultValidators.get( key ); + } + + return delegated.getInstance( key ); + } + + @Override + public void releaseInstance(ConstraintValidator instance) { + delegated.releaseInstance( instance ); + } + + public static class Builder { + + private ConstraintValidatorFactory delegated; + private final Map, ConstraintValidator> defaultValidators = new HashMap<>(); + + private Builder() { + } + + public Builder defaultValidators( + Map, ConstraintValidator> validators) { + this.defaultValidators.putAll( validators ); + return this; + } + + public Builder delegated( + ConstraintValidatorFactory delegated) { + this.delegated = delegated; + return this; + } + + public PreconfiguredConstraintValidatorFactory build() { + return new PreconfiguredConstraintValidatorFactory( this ); + } + } +} diff --git a/test-utils/src/main/java/org/hibernate/validator/testutil/PreconfiguredValidatorsValidatorFactory.java b/test-utils/src/main/java/org/hibernate/validator/testutil/PreconfiguredValidatorsValidatorFactory.java new file mode 100644 index 0000000000..579ad13020 --- /dev/null +++ b/test-utils/src/main/java/org/hibernate/validator/testutil/PreconfiguredValidatorsValidatorFactory.java @@ -0,0 +1,114 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.testutil; + +import jakarta.validation.ClockProvider; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorFactory; +import jakarta.validation.MessageInterpolator; +import jakarta.validation.ParameterNameProvider; +import jakarta.validation.TraversableResolver; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorContext; +import jakarta.validation.ValidatorFactory; + +import java.util.HashMap; +import java.util.Map; + +/** + * This class provides useful functions to create {@code ValidatorFactory} with preconfigured validators to test Bean + * validation without creation of custom validator instances. + * + * @author Attila Hajdu + */ +@SuppressWarnings("rawtypes") +public class PreconfiguredValidatorsValidatorFactory implements ValidatorFactory { + private final Map, ConstraintValidator> defaultValidators; + private final ValidatorFactory delegated; + + private PreconfiguredValidatorsValidatorFactory(Builder builder) { + this.defaultValidators = builder.defaultValidators; + + ValidatorFactory defaultValidationFactory = Validation.buildDefaultValidatorFactory(); + ConstraintValidatorFactory wrappedConstraintValidatorFactory = PreconfiguredConstraintValidatorFactory.builder() + .delegated( defaultValidationFactory.getConstraintValidatorFactory() ) + .defaultValidators( this.defaultValidators ).build(); + + this.delegated = Validation.byDefaultProvider().configure() + .constraintValidatorFactory( wrappedConstraintValidatorFactory ) + .buildValidatorFactory(); + + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public Validator getValidator() { + return delegated.getValidator(); + } + + @Override + public ValidatorContext usingContext() { + return delegated.usingContext(); + } + + @Override + public MessageInterpolator getMessageInterpolator() { + return delegated.getMessageInterpolator(); + } + + @Override + public TraversableResolver getTraversableResolver() { + return delegated.getTraversableResolver(); + } + + @Override + public ConstraintValidatorFactory getConstraintValidatorFactory() { + return delegated.getConstraintValidatorFactory(); + } + + @Override + public ParameterNameProvider getParameterNameProvider() { + return delegated.getParameterNameProvider(); + } + + @Override + public ClockProvider getClockProvider() { + return delegated.getClockProvider(); + } + + @Override + public T unwrap(Class type) { + return delegated.unwrap( type ); + } + + @Override + public void close() { + delegated.close(); + } + + public static class Builder { + + private Builder() { + } + + private final Map, ConstraintValidator> defaultValidators = new HashMap<>(); + + public Builder defaultValidators( + Map, ConstraintValidator> defaultValidators) { + this.defaultValidators.putAll( defaultValidators ); + return this; + } + + public PreconfiguredValidatorsValidatorFactory build() { + return new PreconfiguredValidatorsValidatorFactory( this ); + } + } +}