【问题标题】:Bean Validation: How can I manually create a ConstraintViolation?Bean Validation:如何手动创建 ConstraintViolation?
【发布时间】:2014-05-21 05:31:24
【问题描述】:

我有一个特定的场景,我只能在流程的稍后阶段手动检查违规条件。

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

如何在不让框架通过注释为我完成的情况下自己创建ConstraintViolation(我使用 Hibernate Validator)?

代码示例:

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

谢谢。

【问题讨论】:

  • 到目前为止你的测试代码是什么?
  • 编辑您的问题,添加 sn-p 并使用“代码”按钮对其进行格式化,这样更易​​于阅读:)
  • 谢谢,原问题已更新。
  • 根据您的 sn-p,为什么不抛出并捕获自定义异常以更好地表达正在发生的事情 throw new MissingFilterField(firstFieldToSortBy)
  • 由于堆栈向上,捕获了一个 ConstraintViolation 异常,该异常已将异常转换为客户的预期错误返回值。但我明白你在说什么。也许我应该创建一个新的异常处理场景。

标签: java validation bean-validation hibernate-validator


【解决方案1】:

我已经多次遇到这个问题并想出了这个小解决方案。 它基于 Validator 的 validateValue 方法,并在测试类中使用了一个 helper-validator,用于在一行中生成一个 Set&lt;ConstraintViolation&lt;T&gt;&gt; 对象。

假设您正在测试一个内部使用验证器的类,在我们的测试中模拟,对象“数据数据”具有 Bean 验证注释,例如字段“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。让验证器为您完成这项工作。

【讨论】:

  • 你能解释一下违规生成器是如何实现的吗?
  • 如果没有ViolationsGenerator 实现,这个答案是没有用的。
【解决方案2】:

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

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

【讨论】:

    【解决方案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

    【讨论】:

    • ConstraintViolationImpl.forBeanValidation() 只能从 Hibernate Validator 5.x 开始工作
    【解决方案4】:

    这里有几件事:

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

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

    【讨论】:

      【解决方案5】:

      在我看来,最简单的方法是模拟你的服务在你的测试中抛出约束冲突。例如,您可以通过扩展类来手动完成,也可以使用模拟框架,例如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);
          }
      }
      

      【讨论】:

      • 感谢您的回答。我想我应该说清楚:这不是用于测试目的,它是生产代码。
      • 那么除非你不能改变捕获异常的代码,否则我认为抛出约束冲突是没有意义的。你最好抛出一个专门的异常,这样更容易理解你想要实现的目标
      • 我想说的是,如果我的数字应该 > 5,但实际上它是 1,如果你发现我的偏差,我看不出有很多理由抛出 NullPointerException
      • 感谢您的推荐。我最终决定按照您的建议实现我自己的自定义异常。我仍在使用来自休眠验证器的相同 ValidationMessages,只是我自己解析它。这样我就可以从两个异常中重用相同的错误文本。
      • 太棒了!您可以将以上内容作为答案发布并在几天后将其作为正确答案接受,不一定是其他人的答案。干杯
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-31
      • 1970-01-01
      • 2011-02-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多