如何测试在java中实现ConstraintValidator的Validator?

goe*_*goe 10 java validation testng custom-validators bean-validation

我有一个"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;
    }
}
Run Code Online (Sandbox Code Playgroud)

和相应的接口类:

@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 "";
}
Run Code Online (Sandbox Code Playgroud)

我希望能够编写一个单元测试类来测试该验证器中的直接逻辑.但似乎我用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());
    }
Run Code Online (Sandbox Code Playgroud)

我不想构建模拟模型来测试所有验证器.有没有办法创建一个单独的测试类,只是在一个验证器中直接测试逻辑而不使用任何其他模型类等?

Har*_*rdy 8

您可以独立测试验证器.rub当然是initialize方法,因为它需要一个注释实例.你基本上有三个选择:

  1. 添加第二个初始化方法,该方法直接获取所需的参数.然后,您可以使用此方法初始化验证程序.如果您的测试位于同一个包中,您也可以使此方法只显示包
  2. 将测试注释放在测试类中并通过反射检索它,以便将其传递给initialize方法.
  3. 使用注释代理.这也是Hibernate Validator本身在内部使用的,以防通过XML配置约束或测试需要约束.有两个班在Hibernate验证,你可以使用AnnotationDescriptorAnnotationFactory.代码有点像这样:

-

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 );
}
Run Code Online (Sandbox Code Playgroud)

您需要依赖于Hibernate Validator内部类,但出于测试目的,这应该没问题.当然,您也可以创建自己的代理框架.


JCo*_*ton 8

我使用了以下模式:

@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;
        }

    }

}
Run Code Online (Sandbox Code Playgroud)

我们可以模拟我们正在测试的类。由于注释只是一个接口,我们可以将具体实现作为参数传递给初始化(为了正确初始化您的测试,您可以按照您需要的任何方式进行操作)。然后,您可以将模拟传递ConstraintValidatorContext给您的isValid方法。但是,根据该方法的作用,您可能需要做一些额外的工作,如果它与上下文交互,您可能需要做一些进一步的模拟。


Ram*_*rot 5

注解:

@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 {};
}
Run Code Online (Sandbox Code Playgroud)

验证器:

public class NoWhitespacesValidator implements ConstraintValidator<NoWhitespaces, String> {
    @Override public boolean isValid(String value, ConstraintValidatorContext context) {
        return !value.contains(" ");
    }
}
Run Code Online (Sandbox Code Playgroud)

测试用例:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我想知道这是如何编译的,因为你的“isValid()”方法需要一个 ConstraintValidationContext (2认同)

小智 5

正如@Rammgarot建议的那样,它工作得很好,但是如果我们更改他的代码,例如验证规则必须是下一个:“没有空格”并且必须以“stack”一词开头
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 {};
}
Run Code Online (Sandbox Code Playgroud)



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;

   }
}
Run Code Online (Sandbox Code Playgroud)

测试不会通过,代码将生成 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);
    }


}
Run Code Online (Sandbox Code Playgroud)