I’m working on JUnit tests for constraint validators with dependencies. I created a custom ConstraintValidatorFactory
that has access to Mockito mocks and basically everything is working.
To reduce the initialization overhead I wanted the ValidatorFactory
to be statically initialized only once in a custom JUnit rule and for each test run a new ValidatorContext
would be used.
Unfortunately the constraint validator for a specific “validation point” is requested only once from the constraint validator factory, regardless of the validator context being used (see code example below).
I would have expected that when using a context with a custom constraint validator factory all constraint validators are always requested from this factory and not shared across all contexts. I tested two bean validation providers and both behave identically, so it probably works as designed. But then, what is the typical use-case for a custom constraint validator factory inside a context?
public class ValidatorContextTest {
@Rule
public final MockitoRule mockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
@Mock
private ConstraintValidatorFactory constraintValidatorFactory;
@Test
public void test() {
final ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
when(constraintValidatorFactory.getInstance(TestBeanAnnotationValidator.class))
.thenReturn(new TestBeanAnnotationValidator());
for (int i = 0; i < 2; i++) {
final Validator validator = validatorFactory
.usingContext()
// assumption: constraint validators instantiated by this factory are "bound" to this very context
.constraintValidatorFactory(constraintValidatorFactory)
.getValidator();
validator.validate(new TestBean());
}
// fails with "Wanted 2 times, but was 1 time"
verify(constraintValidatorFactory, times(2)).getInstance(TestBeanAnnotationValidator.class);
}
@TestBeanAnnotation
private static class TestBean {
// no extras
}
@Constraint(validatedBy = {TestBeanAnnotationValidator.class})
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestBeanAnnotation {
String message() default "Test message";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public static class TestBeanAnnotationValidator implements ConstraintValidator<TestBeanAnnotation, TestBean> {
// fields, constructor etc.
public void initialize(TestBeanAnnotation testBeanAnnotation) {
// NOP
}
public boolean isValid(TestBean testBean, ConstraintValidatorContext constraintValidatorContext) {
return false;
}
}
}