Junit test cases for javax Custom Validator - java

I am working on cross field validation using javax validation API in Spring Boot Application. I have a User bean and i have to validate that both firstname and lastname are not null/empty. At least one of this field should have a value.
I have created custom annotation (NameMatch.java) and custom Validator (NameValidator.java) for this requirement.
#NameMatch(first = "firstname", second = "lastname", message = "The first and lastname can't be null")
public class User {
private String firstname;
private String lastname;
#NotNull
#Email
private String email;
#NotNull
private String phone;
}
NameMatch.java
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = NameValidator.class)
#Documented
public #interface NameMatch
{
String message() default "{constraints.fieldmatch}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* #return The first field
*/
String first();
/**
* #return The second field
*/
String second();
}
NameValidator.java
public class NameValidator implements ConstraintValidator<NameMatch, Object>
{
private String firstFieldName;
private String secondFieldName;
#Override
public void initialize(final NameMatch constraintAnnotation)
{
firstFieldName = constraintAnnotation.first();
secondFieldName = constraintAnnotation.second();
}
#Override
public boolean isValid(final Object value, final ConstraintValidatorContext context)
{
boolean isValidName = false;
try
{
final Object firstName = BeanUtils.getProperty(value, firstFieldName);
final Object lastName = BeanUtils.getProperty(value, secondFieldName);
// Validation logic
}
catch (final Exception ignore)
{
}
return isValidName;
}
}
UserValidator.java
public class UserValidator
{
public void isValidUser()
{
//Create ValidatorFactory which returns validator
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
//It validates bean instances
Validator validator = factory.getValidator();
User user = new User();
user.setEmail("test#gmail.com");
user.setPhone("12345678")
//Validate bean
Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);
//Show errors
if (constraintViolations.size() > 0) {
for (ConstraintViolation<User> violation : constraintViolations) {
System.out.println(violation.getMessage());
}
} else {
System.out.println("Valid Object");
}
}
}
I have to write JUnit test cases for the Custom Validator class. I explored hibernate validator docs but couldn't find a way to invoke custom validator method through JUnit. Can someone please help to write JUnit test cases for above scenario.

Your NameValidator has public methods, so you can instantiate an object and write unit tests like for any other public method.
A possible JUnit 5 test with Mockito can look like the following:
#ExtendWith(MockitoExtension.class)
class NameValidatorTest {
#Mock
private NameMatch nameMatch;
#Mock
private ConstraintValidatorContext constraintValidatorContext;
#Test
public void testIsValid() {
when(nameMatch.first()).thenReturn("firstname");
when(nameMatch.second()).thenReturn("lastname");
NameValidator nameValidator = new NameValidator();
nameValidator.initialize(nameMatch);
User user = new User();
user.setFirstname("Duke");
user.setLastname("Duke");
boolean result = nameValidator.isValid(user, constraintValidatorContext);
assertTrue(result);
}
}
Depending of what you need the ConstraintValidatorContext you might also want to mock methods or later verify that specific methods were invoked.
If you are not using JUnit 5, you can adjust the code to not the JUnit 5 MockitoExtension and create the mocks using Mockito.mock().

One way is definitely Mockito (as #rieckpil mentioned).
If you dont want that, and actually want to invoke the validator, you can have something like this:
#SpringBootTest
public class NameValidatorUnitTest {
#Test
public void whenExistingRootRole_thenFail()
{
AnnotationDescriptor<NameMatch> descriptor = new AnnotationDescriptor<NameMatch>( NameMatch.class );
AnnotationFactory.create( descriptor );
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
User user = new User();
user.setEmail("test#gmail.com");
user.setPhone("12345678");
Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);
if (constraintViolations.size() > 0) {
for (ConstraintViolation<User> violation : constraintViolations) {
System.out.println(violation.getMessage());
}
} else {
System.out.println("Valid Object");
}
Assert.assertEquals(true, constraintViolations.size()>0);
}
}

Related

How mix Spring Validator and JSR 303

I want to perform a validation both by using annotations and Spring Validator.
Product.java
public class Product {
private int id;
#NotNull(message = "cannot be null")
private String desc;
// getters and setters
}
ProductValidator.java
public class ProductValidator implements Validator {
public boolean supports(Class<?> aClass) {
return Product.class.equals(aClass);
}
public void validate(Object obj, Errors errors) {
Product p = (Product) obj;
if (p.getId() <= 0) {
errors.rejectValue("id", "id must be positive");
}
}
}
Main.java
public static void main(String[] args) {
Product product = new Product();
DataBinder binder = new DataBinder(product);
binder.addValidators(new ProductValidator());
binder.validate();
System.out.println(binder.getBindingResult());
}
I was expecting two errors in the output but I get only the Spring Validator's one.
What am I missing?
DataBinder validators are initially empty. Implementation
Try adding JSR 303 validator, something like this:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
DataBinder binder = new DataBinder(product);
binder.addValidators(new ProductValidator());
binder.addValidators(new SpringValidatorAdapter(factory.getValidator()));
binder.validate();

How to apply custom validation on a field using javax validation only if it is not null?

I am working on javax validation API in Spring Boot Application. I have a User bean and i have to validate that username given in request is unique and doesn't exist into Database.
I have created custom annotation (UniqueUser.java) and custom Validator(UniqueUserValidator.java) for this requirement.
public class User {
#NotNull
#UniqueUser
private String username;
#NotNull
private String password;
#NotNull
#Email
private String email;
#NotNull
private String phone;
}
UniqueUser.java
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = UniqueUserValidator.class)
#Documented
public #interface NameMatch
{
String message() default "User id already exists";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
UniqueUserValidator.java
public class UniqueUserValidator implements ConstraintValidator<NameMatch, Object>
{
#Autowired
UserRepository userRepository;
#Override
public boolean isValid(String userName, final ConstraintValidatorContext context)
{
boolean isValidUser = false;
if(userName!=null && !userName.isEmpty()) {
Optional<User> user= userRepository.findByUserId(userName);
isValidUser = !user.isPresent();
}
return isValidUser;
}
}
In above code, the unique user validation get called for username field even if the field is null and shows Username already exists error message. I want the custom validator to get called only when username has some value. Is it possible to avoid this call.
I can fix the error by modifying the below method and returning true if username is null, but i want't to avoid this unnecessary call.
public class UniqueUserValidator implements ConstraintValidator<NameMatch, Object>
{
#Autowired
UserRepository userRepository;
#Override
public boolean isValid(String userName, final ConstraintValidatorContext context)
{
boolean isValidUser = false;
if(userName!=null && !userName.isEmpty()) {
Optional<User> user= userRepository.findByUserId(userName);
isValidUser = !user.isPresent();
} else {
isValidUser = true;
}
return isValidUser;
}
}
I think what you are looking for is Grouping constraints;
Groups allow you to restrict the set of constraints applied during
validation. One use case for validation groups are UI wizards where in
each step only a specified subset of constraints should get validated.
The groups targeted are passed as var-arg parameters to the
appropriate validate method.
In your case it should look like this:
#GroupSequence({ FirstConstaint.class, SecondConstaint.class })
public class User {
#NotNull(groups = FirstConstaint.class)
#UniqueUser(groups = SecondConstaint.class)
private String username;
// the rest of your fields
}
interface FirstConstaint {
}
interface SecondConstaint {
}
This way, it will check #UniqueUser only if the field is not null, otherwise, it will return the message of #NotNull validation.
Otherwise like #User said you can use this checks in service layer ^^

Spring ConstraintValidator set custom code/rejected value

I created a custom class-level ConstraintValidator that validates a certain field if another field has a specific value. Here's the object:
#ValidRequest
public class Request {
interface ValidationName {}
#NotBlank(groups = {Default.class})
private final String fieldName;
#NotBlank(groups = {ValidationName.class})
#Length(max = 40, groups = {ValidationName.class})
private final String fieldValue;
// Constructor, getters
}
And the class-level ConstraintValidator:
public class ValidRequestImpl implements
ConstraintValidator<ValidRequest, Request> {
private Validator validator;
#Autowired
public ValidRequestImpl (Validator validator) {
this.validator = validator;
}
#Override
public void initialize(ValidRequest constraintAnnotation) {}
#Override
public boolean isValid(ValidRequest value, ConstraintValidatorContext context) {
if (!validator.validateProperty(value, "fieldName").isEmpty()) {
return true;
}
if (value.getFieldName().equals("name")) {
Set<ConstraintViolation<Request>> violations =
validator.validateProperty(value, "fieldValue", ValidationName.class);
violations.forEach(violation -> context
.buildConstraintViolationWithTemplate(violation.getMessage())
.addPropertyNode("fieldValue")
.addConstraintViolation());
context.disableDefaultConstraintViolation();
return violations.isEmpty();
}
return true;
}
}
This works fine, but the returned errors object isn't very helpful:
"errors": [
{
"message": "Please enter a name.",
"field_name": "request.fieldValue",
"rejected_value": "com.example.Request#5f017a8c",
"code": "ValidRequest"
}
],
Ideally, I would want the rejected_value field to be the actual fieldValue that was passed (in this case empty string), instead of the class-level object. I would also want the code field to be the actual annotation that failed (NotBlank), rather than ValidRequest. context.buildConstraintViolationWithTemplate() doesn't appear to have any methods that allow you to change these fields.
How can I accomplish this?

Validate object field by conditions using Spring

I have a complex object that contains two UserPropertyForm objects inside:
public class ComplexUserForm {
int userType;
#Valid
UserPropertyForm property1;
UserPropertyForm property2;
...
}
public class UserPropertyForm {
#NotEmpty
#Length(max = 255)
private String title;
#NotEmpty
#Length(min = 100)
private String description;
...
}
I need property1 be validated every time, so I have marked it as #Valid.
I need property2 be validated only if userType == 2
Could anyone say if I can validate property2 in a simple way using annotations I have for UserPropertyForm fields?
Thanks for any help.
You can use this custom annotation above your class.
#ValidateIfAnotherFieldHasValue(
fieldName = "userType",
fieldValue = "2",
dependFieldName = "property2")
public class ComplexUserForm {
int userType;
#Valid
UserPropertyForm property1;
UserPropertyForm property2;
It will validate property2 only when getUserType().equals("2").
The error messsages will go in property2.fieldname so you'll need
<form:errors path="property2.*"/> in your JSP if you want to catch all errors together from property2.
public class ValidateIfAnotherFieldHasValueValidator
implements ConstraintValidator<ValidateIfAnotherFieldHasValue, Object> {
private String fieldName;
private String expectedFieldValue;
private String dependFieldName;
#Override
public void initialize(final ValidateIfAnotherFieldHasValue annotation) {
fieldName = annotation.fieldName();
expectedFieldValue = annotation.fieldValue();
dependFieldName = annotation.dependFieldName();
}
#Override
public boolean isValid(final Object value, final ConstraintValidatorContext ctx) {
if (value == null) {
return true;
}
try {
final String fieldValue = BeanUtils.getProperty(value, fieldName);
final Object dependFieldValue = PropertyUtils.getProperty(value, dependFieldName);
if (expectedFieldValue.equals(fieldValue)) {
ctx.disableDefaultConstraintViolation();
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Object>> errorList = validator.validate(dependFieldValue);
for(ConstraintViolation<Object> error : errorList) {
ctx.buildConstraintViolationWithTemplate(error.getMessageTemplate())
.addNode(dependFieldName+"."+error.getPropertyPath())
.addConstraintViolation();
}
return errorList.isEmpty();
}
} catch (final NoSuchMethodException ex) {
throw new RuntimeException(ex);
} catch (final InvocationTargetException ex) {
throw new RuntimeException(ex);
} catch (final IllegalAccessException ex) {
throw new RuntimeException(ex);
}
return true;
}
}
and:
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = ValidateIfAnotherFieldHasValueValidator.class)
#Documented
public #interface ValidateIfAnotherFieldHasValue {
String fieldName();
String fieldValue();
String dependFieldName();
String message() default "{ValidateIfAnotherFieldHasValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#interface List {
ValidateIfAnotherFieldHasValue[] value();
}
}
I've manage to do that in validate method of form's validator:
public void validate(final Object obj, final Errors errors) {
final ComplexUserForm form = (ComplexUserForm) obj;
if (form.getUserType() == 2) {
ClassValidator<UserPropertyForm> offered2Validator = new ClassValidator<UserPropertyForm>(UserPropertyForm.class);
InvalidValue[] property2InvalidValues = property2Validator.getInvalidValues(form.getProperty2());
for (final InvalidValue invalidValue : property2InvalidValues)
errors.rejectValue("property2." + invalidValue.getPropertyPath(), invalidValue.getMessage(), invalidValue.getMessage());
}
}
}
But I had to add "property2." string to the value's path when rejecting some value of property2 field. If someone knows better way I would be glad to know it. Thanks

JSR 303 Validation, If one field equals "something", then these other fields should not be null

I'm looking to do a little custom validation with JSR-303 javax.validation.
I have a field. And If a certain value is entered into this field I want to require that a few other fields are not null.
I'm trying to figure this out. Not sure exactly what I would call this to help find an explanation.
Any help would be appreciated. I am pretty new to this.
At the moment I'm thinking a Custom Constraint. But I'm not sure how to test the value of the dependent field from within the annotation. Basically I'm not sure how to access the panel object from the annotation.
public class StatusValidator implements ConstraintValidator<NotNull, String> {
#Override
public void initialize(NotNull constraintAnnotation) {}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if ("Canceled".equals(panel.status.getValue())) {
if (value != null) {
return true;
}
} else {
return false;
}
}
}
It's the panel.status.getValue(); giving me trouble.. not sure how to accomplish this.
Define method that must validate to true and put the #AssertTrue annotation on the top of it:
#AssertTrue
private boolean isOk() {
return someField != something || otherField != null;
}
The method must start with 'is'.
In this case I suggest to write a custom validator, which will validate at class level (to allow us get access to object's fields) that one field is required only if another field has particular value. Note that you should write generic validator which gets 2 field names and work with only these 2 fields. To require more than one field you should add this validator for each field.
Use the following code as an idea (I've not test it).
Validator interface
/**
* Validates that field {#code dependFieldName} is not null if
* field {#code fieldName} has value {#code fieldValue}.
**/
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Repeatable(NotNullIfAnotherFieldHasValue.List.class) // only with hibernate-validator >= 6.x
#Constraint(validatedBy = NotNullIfAnotherFieldHasValueValidator.class)
#Documented
public #interface NotNullIfAnotherFieldHasValue {
String fieldName();
String fieldValue();
String dependFieldName();
String message() default "{NotNullIfAnotherFieldHasValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Documented
#interface List {
NotNullIfAnotherFieldHasValue[] value();
}
}
Validator implementation
/**
* Implementation of {#link NotNullIfAnotherFieldHasValue} validator.
**/
public class NotNullIfAnotherFieldHasValueValidator
implements ConstraintValidator<NotNullIfAnotherFieldHasValue, Object> {
private String fieldName;
private String expectedFieldValue;
private String dependFieldName;
#Override
public void initialize(NotNullIfAnotherFieldHasValue annotation) {
fieldName = annotation.fieldName();
expectedFieldValue = annotation.fieldValue();
dependFieldName = annotation.dependFieldName();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext ctx) {
if (value == null) {
return true;
}
try {
String fieldValue = BeanUtils.getProperty(value, fieldName);
String dependFieldValue = BeanUtils.getProperty(value, dependFieldName);
if (expectedFieldValue.equals(fieldValue) && dependFieldValue == null) {
ctx.disableDefaultConstraintViolation();
ctx.buildConstraintViolationWithTemplate(ctx.getDefaultConstraintMessageTemplate())
.addNode(dependFieldName)
.addConstraintViolation();
return false;
}
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
throw new RuntimeException(ex);
}
return true;
}
}
Validator usage example (hibernate-validator >= 6 with Java 8+)
#NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldOne")
#NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldTwo")
public class SampleBean {
private String status;
private String fieldOne;
private String fieldTwo;
// getters and setters omitted
}
Validator usage example (hibernate-validator < 6; the old example)
#NotNullIfAnotherFieldHasValue.List({
#NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldOne"),
#NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldTwo")
})
public class SampleBean {
private String status;
private String fieldOne;
private String fieldTwo;
// getters and setters omitted
}
Note that validator implementation uses BeanUtils class from commons-beanutils library but you could also use BeanWrapperImpl from Spring Framework.
See also this great answer: Cross field validation with Hibernate Validator (JSR 303)
You should make use of custom DefaultGroupSequenceProvider<T>:
ConditionalValidation.java
// Marker interface
public interface ConditionalValidation {}
MyCustomFormSequenceProvider.java
public class MyCustomFormSequenceProvider
implements DefaultGroupSequenceProvider<MyCustomForm> {
#Override
public List<Class<?>> getValidationGroups(MyCustomForm myCustomForm) {
List<Class<?>> sequence = new ArrayList<>();
// Apply all validation rules from ConditionalValidation group
// only if someField has given value
if ("some value".equals(myCustomForm.getSomeField())) {
sequence.add(ConditionalValidation.class);
}
// Apply all validation rules from default group
sequence.add(MyCustomForm.class);
return sequence;
}
}
MyCustomForm.java
#GroupSequenceProvider(MyCustomFormSequenceProvider.class)
public class MyCustomForm {
private String someField;
#NotEmpty(groups = ConditionalValidation.class)
private String fieldTwo;
#NotEmpty(groups = ConditionalValidation.class)
private String fieldThree;
#NotEmpty
private String fieldAlwaysValidated;
// getters, setters omitted
}
See also related question on this topic.
Here's my take on it, tried to keep it as simple as possible.
The interface:
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = OneOfValidator.class)
#Documented
public #interface OneOf {
String message() default "{one.of.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String[] value();
}
Validation implementation:
public class OneOfValidator implements ConstraintValidator<OneOf, Object> {
private String[] fields;
#Override
public void initialize(OneOf annotation) {
this.fields = annotation.value();
}
#Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(value);
int matches = countNumberOfMatches(wrapper);
if (matches > 1) {
setValidationErrorMessage(context, "one.of.too.many.matches.message");
return false;
} else if (matches == 0) {
setValidationErrorMessage(context, "one.of.no.matches.message");
return false;
}
return true;
}
private int countNumberOfMatches(BeanWrapper wrapper) {
int matches = 0;
for (String field : fields) {
Object value = wrapper.getPropertyValue(field);
boolean isPresent = detectOptionalValue(value);
if (value != null && isPresent) {
matches++;
}
}
return matches;
}
private boolean detectOptionalValue(Object value) {
if (value instanceof Optional) {
return ((Optional) value).isPresent();
}
return true;
}
private void setValidationErrorMessage(ConstraintValidatorContext context, String template) {
context.disableDefaultConstraintViolation();
context
.buildConstraintViolationWithTemplate("{" + template + "}")
.addConstraintViolation();
}
}
Usage:
#OneOf({"stateType", "modeType"})
public class OneOfValidatorTestClass {
private StateType stateType;
private ModeType modeType;
}
Messages:
one.of.too.many.matches.message=Only one of the following fields can be specified: {value}
one.of.no.matches.message=Exactly one of the following fields must be specified: {value}
A different approach would be to create a (protected) getter that returns an object containing all dependent fields. Example:
public class MyBean {
protected String status;
protected String name;
#StatusAndSomethingValidator
protected StatusAndSomething getStatusAndName() {
return new StatusAndSomething(status,name);
}
}
StatusAndSomethingValidator can now access StatusAndSomething.status and StatusAndSomething.something and make a dependent check.
Sample below:
package io.quee.sample.javax;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validator;
import javax.validation.constraints.Pattern;
import java.util.Set;
/**
* Created By [**Ibrahim Al-Tamimi **](https://www.linkedin.com/in/iloom/)
* Created At **Wednesday **23**, September 2020**
*/
#SpringBootApplication
public class SampleJavaXValidation implements CommandLineRunner {
private final Validator validator;
public SampleJavaXValidation(Validator validator) {
this.validator = validator;
}
public static void main(String[] args) {
SpringApplication.run(SampleJavaXValidation.class, args);
}
#Override
public void run(String... args) throws Exception {
Set<ConstraintViolation<SampleDataCls>> validate = validator.validate(new SampleDataCls(SampleTypes.TYPE_A, null, null));
System.out.println(validate);
}
public enum SampleTypes {
TYPE_A,
TYPE_B;
}
#Valid
public static class SampleDataCls {
private final SampleTypes type;
private final String valueA;
private final String valueB;
public SampleDataCls(SampleTypes type, String valueA, String valueB) {
this.type = type;
this.valueA = valueA;
this.valueB = valueB;
}
public SampleTypes getType() {
return type;
}
public String getValueA() {
return valueA;
}
public String getValueB() {
return valueB;
}
#Pattern(regexp = "TRUE")
public String getConditionalValueA() {
if (type.equals(SampleTypes.TYPE_A)) {
return valueA != null ? "TRUE" : "";
}
return "TRUE";
}
#Pattern(regexp = "TRUE")
public String getConditionalValueB() {
if (type.equals(SampleTypes.TYPE_B)) {
return valueB != null ? "TRUE" : "";
}
return "TRUE";
}
}
}

Categories