如何测试在 Java 中实现 ConstraintValidator 的验证器?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28768577/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 14:09:41  来源:igfitidea点击:

How to test a Validator which implements ConstraintValidator in java?

javavalidationtestngbean-validationcustom-validators

提问by goe

I have an "AllowedValuesValidator.java" class:

我有一个“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;
    }
}

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?

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

回答by Hardy

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:

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

  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 AnnotationDescriptorand AnnotationFactory. The code would somewhat like this:
  1. 添加第二个 initialize 方法,该方法直接采用所需的参数。然后,您可以使用此方法来初始化验证器。如果您的测试驻留在同一个包中,您也可以使此方法仅包可见
  2. 将测试注释放在测试类中的某个位置,并通过反射检索它,以便将其传递给 initialize 方法。
  3. 使用注释代理。这也是 Hibernate Validator 本身在内部使用的,以防通过 XML 配置约束或测试需要。Hibernate Validator 中有两个类可以使用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 );
}

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.

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

回答by JCollerton

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 ConstraintValidatorContextto your isValidmethod. 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.

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

回答by Rammgarot

Annotation:

注解:

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

Validator:

验证器:

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

TestCase:

测试用例:

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