[英]How to extend a built-in Hibernate Validator ConstraintValidator
[英]How to test a Validator which implements ConstraintValidator in java?
我有一個“AllowedValuesValidator.java”類:
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;
}
}
以及對應的接口類:
@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 "";
}
我希望能夠編寫一個單元測試類來測試該驗證器中的直接邏輯。 但似乎我在 google 上搜索的大多數地方都提供了測試類的示例,我們基本上測試了給定 Model 類的所有驗證器,例如:
@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());
}
我不想構建模擬模型來測試所有驗證器。 有沒有辦法創建一個單獨的測試類來直接在一個驗證器中測試邏輯而不使用任何其他模型類等?
您可以獨立測試驗證器。 rub 當然是 initialize 方法,因為它需要一個注解的實例。 你基本上有三個選擇:
AnnotationDescriptor
和AnnotationFactory
。 代碼有點像這樣:——
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 );
}
您需要依賴 Hibernate Validator 內部類,但出於測試目的,這應該沒問題。 當然,您也可以創建自己的代理框架。
我使用了以下模式:
@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;
}
}
}
我們可以模擬我們正在測試的類。 由於注解只是一個接口,我們可以將具體實現作為參數傳遞給初始化(為了正確初始化測試,您可以以任何需要的方式使其行為)。 然后,您可以將模擬ConstraintValidatorContext
傳遞給您的isValid
方法。 但是,根據該方法的作用,您可能需要做一些額外的工作,如果它與上下文交互,您可能需要做一些進一步的模擬。
注解:
@Documented
@Constraint(validatedBy = NoWhitespacesValidator.class)
@Target({ FIELD })
@Retention(RUNTIME)
public @interface NoWhitespaces {
String message() default "Not valid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
驗證器:
public class NoWhitespacesValidator implements ConstraintValidator<NoWhitespaces, String> {
@Override public boolean isValid(String value, ConstraintValidatorContext context) {
return !value.contains(" ");
}
}
測試用例:
class NoWhitespacesTest {
private NoWhitespacesValidator validator = new NoWhitespacesValidator();
@Nested
class NoWhitespaceValidFlow {
@Test
void isValid_shouldReturnTrue_whenNoWhitespaces() {
assertTrue(isValid(""));
assertTrue(isValid("foo.bar"));
}
}
@Nested
class NoWhitespacesInvalidFlow {
@Test
void isValid_shouldReturnFalse_whenAtLeastOneWhitespace() {
assertFalse(isValid(" "));
assertFalse(isValid("foo bar"));
assertFalse(isValid(" foo"));
assertFalse(isValid("foo "));
}
}
private boolean isValid(String value) {
return validator.isValid(value, null);
}
}
@Hardy 的回答非常好,但是如果你使用hibernate-validator-6.5.1.Final ,@Hardy 使用的AnnotationDescriptor的構造函數有另一個參數,它也是私有的。 您可以在此處查看 AnnotationDescriptor 構造函數。 所以我到目前為止所做的只是結合@Rammgarot 和@JCollerton 的答案。 這是測試驗證器的另一種方法。
public class AllowedValuesValidatorTest {
private AllowedValuesValidator validator;
String[] allowedValues = {"Y", "N"};
String defaultValue = "";
@BeforeEach
void setUp() {
validator = new AllowedValuesValidator();
validator.initialize(createAnnotation(allowedValues, defaultValue));
}
@Test
void isValid() {
// your test here
}
private boolean isValid(String value) {
return validator.isValid(value, null);
}
private AllowedValues createAnnotation(String[] allowedValues, String defaultValue) {
return new AllowedValues() {
@Override
public Class<? extends Annotation> annotationType() {
return null;
}
@Override
public String message() {
return "Please provide a valid id";
}
@Override
public Class<?>[] groups() {
return new Class[0];
}
@Override
public Class<? extends Payload>[] payload() {
return new Class[0];
}
@Override
public String[] allowedValues() {
return allowedValues;
}
@Override
public String defaultValue() {
return defaultValue;
}
};
}
}
正如@Rammgarot 建議的那樣,它工作正常,但是如果我們更改他的代碼,例如驗證規則必須是下一個:“沒有空格”並且必須以“堆棧”一詞開頭
NoWhitesSpace.java
@Documented
@Constraint(validatedBy = NoWhitespacesValidator.class)
@Target({ FIELD })
@Retention(RUNTIME)
public @interface NoWhitesSpace {
String value() default "stack";
String message() default "Not valid";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
NoWhitespacesValidator.java
public class NoWhitespacesValidator implements ConstraintValidator<NoWhitesSpace, String> {
String prefix;
public void initialize(NoWhitesSpace constraint) {
prefix = constraint.value();
}
public boolean isValid(String value, ConstraintValidatorContext context) {
boolean result = !value.contains(" ") && value.startsWith(prefix);
return result;
}
}
測試不會通過,代碼將生成 NullPointerException。
對我來說,我用模擬找到了解決方案。
-> Customer 是一個簡單的類實體,包含 fields 、setter 和 getter。
NoWhitespacesValidatorTest.java
class NoWhitespacesValidatorTest {
private NoWhitesSpace noWhitesSpace = mock(NoWhitesSpace.class);
private ConstraintValidatorContext constraintValidatorContext = mock(ConstraintValidatorContext.class);
@Test
public void itShouldBeValidWhenItStartsWithPrefixAndWithoutWhiteSpace(){
when(noWhitesSpace.value()).thenReturn("stack");
NoWhitespacesValidator noWhitespacesValidator=new NoWhitespacesValidator();
noWhitespacesValidator.initialize(noWhitesSpace);
Customer customer = new Customer();
customer.setCourseCode("stack");
boolean result = noWhitespacesValidator.isValid(customer.getCourseCode(),constraintValidatorContext);
assertTrue(result);
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.