簡體   English   中英

如何測試在java中實現ConstraintValidator的Validator?

[英]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 方法,因為它需要一個注解的實例。 你基本上有三個選擇:

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

您需要依賴 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM