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()
.