Skip to content

Commit

Permalink
HV-1921 Create a tester library for constraint validators with depend…
Browse files Browse the repository at this point in the history
…ency injection
  • Loading branch information
shark300 committed Dec 23, 2023
1 parent feb157a commit a9c00b3
Show file tree
Hide file tree
Showing 9 changed files with 374 additions and 1 deletion.
12 changes: 11 additions & 1 deletion documentation/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
<!-- Skip artifact deployment -->
<maven.deploy.skip>true</maven.deploy.skip>
<gpg.skip>true</gpg.skip>
<surefire.jvm.args.additional>-Duser.language=en -Duser.country=US</surefire.jvm.args.additional>
<surefire.jvm.args.additional>--add-opens java.base/java.lang=ALL-UNNAMED -Duser.language=en -Duser.country=US</surefire.jvm.args.additional>

<forbiddenapis-junit.path>forbidden-allow-junit.txt</forbiddenapis-junit.path>
<hibernate-validator-parent.path>..</hibernate-validator-parent.path>
Expand All @@ -61,6 +61,11 @@
<artifactId>hibernate-validator-cdi</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>hibernate-validator-test-utils</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
Expand Down Expand Up @@ -97,6 +102,11 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<scope>test</scope>
</dependency>
<!-- Only use assertj when strictly necessary in the documentation tests -->
<dependency>
<groupId>org.assertj</groupId>
Expand Down
55 changes: 55 additions & 0 deletions documentation/src/main/asciidoc/ch06.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
----
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-test-utils</artifactId>
<version>{hvVersion}</version>
<scope>test</scope>
</dependency>
----
====

.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, <<example-using-validator-dependency>> 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

Expand Down
Original file line number Diff line number Diff line change
@@ -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<ConstraintViolation<Person>> constraintViolations = validator.validate( person );

assertEquals( 0, constraintViolations.size() );

verify( zipCodeValidator );
}
}
//end::field[]
Original file line number Diff line number Diff line change
@@ -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[]
Original file line number Diff line number Diff line change
@@ -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<? extends Payload>[] payload() default {};
}
//end::include[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.hibernate.validator.referenceguide.chapter06.customvalidatorwithdependency;

public interface ZipCodeRepository {
boolean isExist(String zipCode);
}
Original file line number Diff line number Diff line change
@@ -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<ZipCode, String> {

@Inject
public ZipCodeRepository zipCodeRepository;

@Override
public boolean isValid(String zipCode, ConstraintValidatorContext constraintContext) {
if ( zipCode == null ) {
return true;
}

return zipCodeRepository.isExist( zipCode );
}
}
//end::include[]
Original file line number Diff line number Diff line change
@@ -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 <http://www.apache.org/licenses/LICENSE-2.0>.
*/
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<Class<? extends ConstraintValidator>, 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 extends ConstraintValidator<?, ?>> T getInstance(Class<T> 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<Class<? extends ConstraintValidator>, ConstraintValidator<?, ?>> defaultValidators = new HashMap<>();

private Builder() {
}

public Builder defaultValidators(
Map<Class<? extends ConstraintValidator>, 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 );
}
}
}

0 comments on commit a9c00b3

Please sign in to comment.