5

I'm trying to build a set of constraint validators for my Spring Boot application. I want to build some validation annotations like @NotNull. Btw: The validations should support validation groups.

So I have a simple item model with a validation annotation:

public class Item {
    @NotNull(groups=OnCreate.class) // Not Null on validation group 'OnCreate'
    private String mayNotBeNull;

    // Constructors and getter/setter stuff.
}

Then I wrapped the persistence logic using a validated service:

@Service
@Validated
public class MyService {
    public Item create(@Validated(OnCreate.class) Item item) {
        Item savedItem = repository.save(item);
        return savedItem;
    }
}

Now I want to test this service without starting a full blown MVC test (which would start all REST controllers and stuff I do not need).

I started to write my test:

@ContextConfiguration(classes = {
ItemRepository.class, MyService.class, LocalValidatorFactoryBean.class
})
@RunWith(SpringRunner.class)
public class PlantServiceTest {

  @MockBean
  private ItemRepository repository;

  @Autowired
  private MyService service;

  @Autowired
  private Validator validator;

  @Test
  public void shouldDetectValidationException() {
        // ... building an invalid item
        Item item = new Item();
        item.setMayNotBeNull(null); // <- This causes the validation exception.
        validator.validate(item, OnCreate.class);
  }

  @Test
  public void shouldAlsoDetectValidationException() {
        // ... building an invalid item
        Item item = new Item();
        item.setMayNotBeNull(null); // <- This should cause the validation exception.
        service.create(item); // <- No validation error. Service is not validating.
  }
  }

The method shouldThrowValidationException detects the validation error, because the field value in item is null.

The method shouldAlsoDetectValidationException does not detect the validation error.

I think I missed something when configuring the context. The service object is not extended by the validation logic.

How can I configure the test so that the autowired services are decorated with the validation logic provided by @Validated?

UNIQUEorn
  • 420
  • 6
  • 17
  • Remove Validate from the class and replace Validated with Valid on the method parameter. – Simon Martinelli Dec 11 '17 at 12:39
  • Oh I should have said that I have to use `@Validated` because it is the only annotation for validation that supports validation groups. I will change my post. – UNIQUEorn Dec 11 '17 at 12:51

2 Answers2

7

@Validated does not work as expected on Parameters. You have to use @Valid on the parameter and add the @Validated with the group on the method or class level.

This way it works:

@Service
@Validated
public class MyService {

    @Validated(OnCreate.class)
    public Item create(@Valid Item item) {
        ...
    }
}

Unfortunately I found no way to have the group on the parameter level.

If you want to test your validation logic in a Spring Unit Test, then you must import the ValidationAutoConfiguration class via:

@Import(ValidationAutoConfiguration.class)
Andrew T Finnell
  • 13,417
  • 3
  • 33
  • 49
Simon Martinelli
  • 34,053
  • 5
  • 48
  • 82
  • Hm, that seems to work at application runtime, when the application runs with its full configuration. But in a JUnit test, the service logic is called without validating the arguments. Do I need something else in the context configuration to get the decorated service with validation? – UNIQUEorn Dec 11 '17 at 13:44
  • 2
    Yes you have to annotate your test with SpringBootTest to have fully Spring Boot up and running or create MethodValidationPostProcessor in your test – Simon Martinelli Dec 11 '17 at 13:50
  • Ah great!!! Thank you! After fiddling around with the `MethodValidationPostProcessor` I got it working!!! – UNIQUEorn Dec 11 '17 at 14:08
  • 2
    In my case, I wanted validation for @ Service classes with a test class that used @ DataJpaTest from Spring Boot. I had to add @ Import(ValidationAutoConfiguration::class) to the class for validation to kick in (i;e., for the MethodValidationPostProcessor to be defined) – dSebastien Dec 30 '17 at 15:49
2

You can do it like:

@Spy private Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

...

Object object = new...
Method method = %your_class%.class.getMethod("%method name%", param_1.class, ..., param_n.class);
        Object[] parameterValues = { 1L, 0, null }; //e.g.
        Set<ConstraintViolation<%your_class%>> violations = executableValidator.validateParameters(object, method, parameterValues);

assertEquals(%number_of_violations%, violations.size());
Rammgarot
  • 1,467
  • 14
  • 9
  • The answer of @SimonMartinelli was the correct one. Actually the position of `@Validated(OnCreate.class)` was just wrong. – UNIQUEorn Feb 27 '19 at 10:35