Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test a Validator which implements ConstraintValidator in java?

Tags:

I have an "AllowedValuesValidator.java" class:

public class AllowedValuesValidator implements ConstraintValidator<AllowedValues, String> {      String[] values;     String defaultValue;      @Override     public void initialize(AllowedValues constraintAnnotation) {         values = constraintAnnotation.allowedValues();         defaultValue = constraintAnnotation.defaultValue();     }      @Override     public boolean isValid(String value, ConstraintValidatorContext context) {         if (!StringUtils.isEmpty(defaultValue) && StringUtils.isEmpty(value)) {             value = defaultValue;         }          if (!StringUtils.isEmpty(value) && !Arrays.asList(values).contains(value)) {             return false;         }         return true;     } } 

And the corresponding interface class:

@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = AllowedValuesValidator.class) public @interface AllowedValues {      String message();      String fieldName();      int fieldNumber();      String[] allowedValues() default {"Y", "N"};      String defaultValue() default ""; } 

I want to be able to write a unit test class to test the direct logic in that validator. But it seems that most places I googled give examples of test classes where we basically test all validators for a given Model class, for example:

    @BeforeClass     public static void setup() {         ValidatorFactory factory = Validation.buildDefaultValidatorFactory();         validator = factory.getValidator();     }      @Test     public void testEmailExistsIncorrect() {          Set<constraintviolation<usercredentialsdto>> violations = validator                 .validate(credentials, UserCredentialsDto.class);         Assert.assertEquals(1, violations.size());     } 

I don't want to build mock models to test all validators. Is there a way to create a separate test class for just testing the logic in one single validator directly without using any other model classes etc?

like image 950
goe Avatar asked Feb 27 '15 15:02

goe


People also ask

What methods should you implement for your custom Validator?

Implementing the Validator Interface A Validator implementation must contain a constructor, a set of accessor methods for any attributes on the tag, and a validate method, which overrides the validate method of the Validator interface.

What is JPA Validator?

Data validation is a common task that occurs in all layers of an application, including persistence. The Java™ Persistence API (JPA) 2.0 provides support for the Bean Validation API so that data validation can be done at run time.

How does the validation class work in Java?

The validation class implements the ConstraintValidator interface, and must also implement the isValid method; it's in this method that we defined our validation rules. Naturally, we're going with a simple validation rule here in order to show how the validator works.

How does a constraint validator work?

Naturally, we're going with a simple validation rule here in order to show how the validator works. ConstraintValidator defines the logic to validate a given constraint for a given object. Implementations must comply with the following restrictions:

What type of testing is uservalidator doing?

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.

How to initialize a test validator using annotation?

You can then use this method to initialize the validator. You can also make this method just package visible, provided your test resides in the same package Place the test annotation somewhere into your test class and retrieve it via reflection in order to pass it to the initialize method. Use annotation proxies.


2 Answers

You can test the validator standalone. The rub is of course the initialize method, since it needs an instance of the annotation. You basically have three options:

  1. Add a second initialize method which takes the required parameters directly. You can then use this method to initialize the validator. You can also make this method just package visible, provided your test resides in the same package
  2. Place the test annotation somewhere into your test class and retrieve it via reflection in order to pass it to the initialize method.
  3. Use annotation proxies. This is also what Hibernate Validator itself uses internally for in case constraints are configured via XML or needed for tests. There are two classes in Hibernate Validator which you could use AnnotationDescriptor and AnnotationFactory. The code would somewhat like this:

--

private AllowedValues createAnnotation(String[]values, String defaultValue) {   AnnotationDescriptor<AllowedValues> descriptor = new AnnotationDescriptor<AllowedValues>( AllowedValues.class );   descriptor.setValue( "values", values );   descriptor.setValue( "defaultValue", defaultValue );    return AnnotationFactory.create( descriptor ); } 

You would need to depend on Hibernate Validator internal classes, but for testing purposes this should be fine. Of course you could also just create your own proxy framework.

like image 120
Hardy Avatar answered Sep 23 '22 15:09

Hardy


I used the below pattern:

@RunWith(MockitoJUnitRunner.class) public class AllowedValuesValidatorTest {      @Mock     AllowedValuesValidator allowedValuesValidator;      @Mock     ConstraintValidatorContext constraintValidatorContext;      @Before     public void setUp() {          doCallRealMethod().when(allowedValuesValidator).initialize(any());         when(allowedValuesValidator.isValid(any(), any())).thenCallRealMethod();          AllowedValuesValidatorTestClass testClass = new AllowedValuesValidatorTestClass();          allowedValuesValidator.initialize(testClass);      }      @Test     public void testIsValidWithValidValues() {         assertTrue(allowedValuesValidator.isValid("Value", constraintValidatorContext));     }      private class AllowedValuesValidatorTestClass implements AllowedValues {          @Override         public String message() {             return "Test Message";         }          @Override         public Class<?>[] groups() {             return new Class[]{};         }          @Override         public Class<? extends Payload>[] payload() {             return new Class[]{};         }          @Override         public Class<? extends Annotation> annotationType() {             return AllowedValues.class;         }      }  } 

We can mock the class we are testing. As an annotation is just an interface we can pass in a concrete implementation as the parameter to initialise (which you can make behave any way you need in order to initialise your test correctly). You can then pass in a mock ConstraintValidatorContext to your isValid method. However, you may need to do some extra work depending on what that method does, if it interacts with the context you may need to do some further mocking.

like image 31
JCollerton Avatar answered Sep 23 '22 15:09

JCollerton