Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bean Validation: How can I manually create a ConstraintViolation?

I have a specific scenario where I can only check for violation conditions manually, at a later point in the flow.

What I want to do is throw a ConstraintViolationException, and provide a "real" ConstraintViolation object to it (when I catch the exception up the stack, I use the #{validatedValue} and violation.getPropertyPath() parameters).

How can I create a ConstraintViolation myself without having the framework do it for me via annotations (I use Hibernate Validator)?

Code example:

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

Thanks.

like image 300
odedia Avatar asked Apr 08 '14 13:04

odedia


4 Answers

In my opinion, the simplest way would be to mock your service into throwing the constraint violation in your test. You can do it manually by extending the class for example, or you can use a mocking framework such as mockito. I prefer mocking frameworks because they simplify things a lot as you neither have to create and maintain additional classes nor have to deal with injecting them in your objects under test.

Taking mockito as a starting point you'd probably write something similar to:

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);
    }
}
like image 151
Morfic Avatar answered Oct 17 '22 10:10

Morfic


One more reason why I don't like Hibernate Validator that particular. They make it really hard to create a simple violation programmatically, when it should be dead simple. I do have test code where I need to create a violation to feed to my mocked subsystem.

Anyway, short of rolling your own implementation of a violation contraint - here is what I do to create a violation for a field:

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

like image 24
Stefan Haberl Avatar answered Oct 17 '22 10:10

Stefan Haberl


A couple of things here:

  1. ConstraintViolation is an interface, so you could just implement your own version

  2. Hibernate Validator uses its own internal implementation of this interface - org.hibernate.validator.internal.engine.ConstraintViolationImpl. It is a public class, but since it is in an internal package you are not encouraged to use it directly. However, you might get an idea what is needed to implement ConstraintViolation.

like image 4
Hardy Avatar answered Oct 17 '22 11:10

Hardy


Why not inject the Validator in your test and create an object triggering the validation errors you want?

Set<ConstraintViolation<T>> res = validator.validate(object);
like image 3
headstar Avatar answered Oct 17 '22 10:10

headstar