Unit Testing Spring Boot Custom Validator

In this blog post, I will explain how to write unit tests for validating the logic of custom validator in Spring Boot applications

In one of my previous blog post , I have covered about creating custom validations and applying them to request in spring boot applications. Now Let’s see how to test the custom validator logic by writing unit tests.

Let’s see the validator logic again

@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = ConditionalNotNullValidator.class)
@Documented
@Repeatable(ConditionalNotNull.List.class)
public @interface ConditionalNotNull {
    String message() default "{validation.conditionalNotNull}";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
    String fields() default "";
    String dependsOn() default "";

    @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE })
    @Retention(RUNTIME)
    @Documented
    @interface List {
        ConditionalNotNull[] value();
    }
}Code language: Java (java)
public class ConditionalNotNullValidator implements ConstraintValidator<ConditionalNotNull, Object> {

    List<String> fields;
    String dependsOnField;

    @Override
    public void initialize(ConditionalNotNull constraintAnnotation) {
        fields = Arrays.stream(constraintAnnotation.fields().split(",")).collect(Collectors.toList());
        dependsOnField = constraintAnnotation.dependsOn();

    }

    @Override
    public boolean isValid(Object o, ConstraintValidatorContext context) {

        if(o == null) {
            return true;
        }


        Object fieldValue = getFieldValue(dependsOnField,o);

       List<String> errorFields =  fields.stream().filter(f -> getFieldValue(f,o) == null).collect(Collectors.toList());

        if(fieldValue !=null && errorFields.isEmpty()) {
            return true;
        }

        if(fieldValue ==null && errorFields.isEmpty()) {

            context.disableDefaultConstraintViolation();
            context
                    .buildConstraintViolationWithTemplate("{validation.custom.conditionalNull}")
                   .addConstraintViolation();
            return false;
        }

        ((HibernateConstraintValidatorContext)context.unwrap(HibernateConstraintValidatorContext.class)).addMessageParameter("fields",String.join(",",errorFields));

        return false;
    }



    public static Object getFieldValue(String fieldName,Object object) {
        try {
            Field field = object.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            return  field.get(object);
        }  catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}
Code language: Java (java)

The above custom validation checks that list of fields are not null based on the dependsOn field value.

Writing Unit Tests

  1. Write Unit Test Class
@ExtendWith(MockitoExtension.class)
public class ConditionalNotNullValidatorTest {

  
}Code language: Java (java)

2.Write new model class to apply the annotation

We will write model class where we can apply the annotation which can be used to test our logic.

This class helps us to test our custom logic in isolation without interfering with other validators.

@ExtendWith(MockitoExtension.class)
public class ConditionalNotNullValidatorTest {

 

    @ConditionalNotNull (fields = "field1,field2", dependsOn = "field3")
    private static final class ModelClass {
        Object field1;
        Object field2;
        Object field3;


        public ModelClass(Object field1, Object field2, Object field3) {
            this.field1 = field1;
            this.field2 = field2;
            this.field3 = field3;
        }
    }
    private static ConditionalNotNull givenAnnotation() {
        return ModelClass.class.getAnnotation(ConditionalNotNull.class);
    }
}
Code language: Java (java)

When we create object for the ModelClass, based on our custom validation logic , if field3 is not null then field1 and field2 should not be null.

Writing Unit Tests

  • case 1) When Object is null then validation should pass
  • case 2) When all fields are not null validation should pass
  • case 3) When dependsOn field ( field3) is not null but one of field is null validation should fail.

@ExtendWith(MockitoExtension.class)
public class ConditionalNotNullValidatorTest {

    private ConditionalNotNullValidator validator;

    @Mock
    protected HibernateConstraintValidatorContext hibernateConstraintValidatorContext;

    @Mock
    protected ConstraintValidatorContext constraintValidatorContext;

    @BeforeEach
    public void setup() {
        validator = new ConditionalNotNullValidator();
    }

    @Test
    public void whenObjectIsNull_validationPasses(){
        assertTrue(validator.isValid(null,constraintValidatorContext));
    }

    @Test
    public void whenAllFieldValuesArePresent_validationPasses() {
        ModelClass modelObject = new ModelClass("testfield1","testfield2","testfield3");
        validator.initialize(givenAnnotation());
        assertTrue( validator.isValid(modelObject,constraintValidatorContext));
    }

    @Test
    public void whenOneFieldValuesIsNull_validationFails() {
        when(constraintValidatorContext.unwrap(HibernateConstraintValidatorContext.class)).thenReturn(hibernateConstraintValidatorContext);
        ModelClass modelObject = new ModelClass("testfield1",null,"testfield3");
        validator.initialize(givenAnnotation());
        assertFalse( validator.isValid(modelObject,constraintValidatorContext));
    }

   @Test
    public void whenOneFieldValuesIsNull2_validationFails() {
        when(constraintValidatorContext.unwrap(HibernateConstraintValidatorContext.class)).thenReturn(hibernateConstraintValidatorContext);
        ModelClass modelObject = new ModelClass(null,"testfield2","testfield3");
        validator.initialize(givenAnnotation());
        assertFalse( validator.isValid(modelObject,constraintValidatorContext));
    }

    @ConditionalNotNull (fields = "field1,field2", dependsOn = "field3")
    private static final class ModelClass {
        Object field1;
        Object field2;
        Object field3;


        public ModelClass(Object field1, Object field2, Object field3) {
            this.field1 = field1;
            this.field2 = field2;
            this.field3 = field3;
        }
    }
    private static ConditionalNotNull givenAnnotation() {
        return ModelClass.class.getAnnotation(ConditionalNotNull.class);
    }
}Code language: Java (java)

Similar Posts