2

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.

Hakan Dilek
  • 2,178
  • 2
  • 23
  • 35
user3244519
  • 661
  • 5
  • 18
  • 36
  • you are already doing it in `UserValidator`, It is the same for unit test, what is the issue? – Kavithakaran Kanapathippillai Jul 19 '20 at 19:25
  • @Kavithakaran Kanapathippillai: UserValidator is doing integration testing. I want to test NameValidator's isValid method through Junit Test cases as part of Unit testing. It isrequired for getting unit test code coverage. – user3244519 Jul 19 '20 at 19:33
  • Reference for junit for validator class https://stackoverflow.com/questions/28768577/how-to-test-a-validator-which-implements-constraintvalidator-in-java – Naruto Jul 20 '20 at 07:41
  • @user3244519 You have 2 options - 1) without mockito and actually testing the annotation and validator (the approach I mentioned) 2) using mockito and mocking all the methods, and just testing the return type of `isValid` method. (the approach which rieckpil mentioned) – Abhinaba Chakraborty Jul 20 '20 at 14:15

2 Answers2

7

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

rieckpil
  • 10,470
  • 3
  • 32
  • 56
0

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);
  }
  

}

Abhinaba Chakraborty
  • 3,488
  • 2
  • 16
  • 37