[英]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.