Bean验证:如何手动创建ConstraintViolation?

时间:2014-04-08 13:21:48

标签: java validation bean-validation hibernate-validator

我有一个特定的场景,我只能在流程的后期手动检查违规情况。

我想要做的是抛出一个ConstraintViolationException,并提供一个真实的" ConstraintViolation object它(当我在堆栈中捕获异常时,我使用#{validatedValue}violation.getPropertyPath()参数)。

如何在没有框架通过注释(我使用Hibernate Validator)为我做的情况下自己创建ConstraintViolation

代码示例:

List<String> columnsListForSorting = new ArrayList<String>(service.getColumnsList(domain));
Collections.sort(columnsListForSorting);

String firstFieldToSortBy = this.getTranslatedFieldName(domain.getClass().getCanonicalName(), sortingInfo.getSortedColumn());
if (!columnsListForSorting.contains(firstFieldToSortBy)){
    throw new ConstraintViolationException(<what here?...>);
}

感谢。

5 个答案:

答案 0 :(得分:3)

这里有几件事:

  1. ConstraintViolation 是一个界面,因此您可以实现自己的版本

  2. Hibernate Validator使用自己的内部实现 - org.hibernate.validator.internal.engine.ConstraintViolationImpl 。它是一个公共类,但由于它位于内部包中,因此不鼓励您直接使用它。但是,您可能知道实现 ConstraintViolation 所需的内容。

答案 1 :(得分:3)

我不喜欢Hibernate Validator的另一个原因。他们让非常难以编程方式创建一个简单的违规行为,当它应该是简单的。我测试代码,我需要创建一个违规来提供给我的模拟子系统。

无论如何,还没有推出你自己的违规禁令实施 - 这就是我为一个字段创建违规行为的方法:

private static final String MESSAGE_TEMPLATE = "{messageTemplate}";
private static final String MESSAGE = "message";

public static <T, A extends Annotation> ConstraintViolation<T> forField(
  final T rootBean, 
  final Class<T> clazz,
  final Class<A> annotationClazz,
  final Object leafBean, 
  final String field, 
  final Object offendingValue) {

  ConstraintViolation<T> violation = null;
  try {
    Field member = clazz.getDeclaredField(field);
    A annotation = member.getAnnotation(annotationClazz);
    ConstraintDescriptor<A> descriptor = new ConstraintDescriptorImpl<>(
      new ConstraintHelper(), 
      member, 
      annotation, 
      ElementType.FIELD);
    Path p = PathImpl.createPathFromString(field);
    violation = ConstraintViolationImpl.forBeanValidation(
      MESSAGE_TEMPLATE, 
      MESSAGE, 
      clazz, 
      rootBean, 
      leafBean,
      offendingValue, 
      p, 
      descriptor, 
      ElementType.FIELD);
  } catch (NoSuchFieldException ignore) {}
  return violation;

}

HTH

答案 2 :(得分:1)

在我看来,最简单的方法是将您的服务模拟为在测试中抛出约束违规。例如,您可以通过扩展类来手动执行此操作,也可以使用mockito之类的模拟框架。我更喜欢模拟框架,因为它们简化了很多事情,因为你既不需要创建和维护其他类,也不必处理在测试对象中注入它们。

以mockito为起点,你可能会写一些类似于:

的东西
import org.hibernate.exception.ConstraintViolationException;
import org.mockito.InjectMocks;
import org.mockito.Mock;

import static org.mockito.Mockito.when;


public class MyTest {
    @Mock /* service mock */
    private MyService myService;

    @InjectMocks /* inject the mocks in the object under test */
    private ServiceCaller serviceCaller;

    @Test
    public void shouldHandleConstraintViolation() {
        // make the mock throw the exception when called
        when(myService.someMethod(...)).thenThrow(new ConstraintViolationException(...))

        // get the operation result
        MyResult result = serviceCaller.doSomeStuffWhichInvokesTheServiceMethodThrowingConstraintViolation();

        // verify all went according to plan
        assertWhatever(result);
    }
}

答案 3 :(得分:0)

为什么不在测试中注入Validator并创建一个触发所需验证错误的对象?

Set<ConstraintViolation<T>> res = validator.validate(object);

答案 4 :(得分:0)

我已经多次遇到此问题,并提出了这个小解决方案。 它基于Validator的validateValue方法,并在测试类中使用辅助验证器,该工具用于在一个衬里中生成Set<ConstraintViolation<T>>对象。

假设您正在测试一个类,该类内部使用在我们的测试中模拟的Validator,该对象在具有Bean Validation批注的对象“ Data data”上使用,例如,字段“ sessionId”用@NotNull批注。

您的测试课程将至少具有以下两个成员:

@Mock
private Validator validator;

private Validator violationCreatorValidator;

@Before
public void setup() {
     MockitoAnnotations.initMocks(this);
     violationsGenerator.Validation.buildDefaultValidatorFactory().getValidator();
}

然后在测试用例中:

when(validator.validate(data))
.thenReturn(violationsGenerator.validateValue(Data.class, "sessionId", null));

就这样。无需实现ConstraintViolation接口或调用巨大的ConstraintViolationImpl.forBeanValidation。让验证器为您完成这项工作。