Spring Boot: How to test a service in JUnit with @Validated annotation?
Asked Answered
D

3

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?

Dufresne answered 11/12, 2017 at 12:21 Comment(2)
Remove Validate from the class and replace Validated with Valid on the method parameter.Ryan
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.Dufresne
R
9

@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)
Ryan answered 11/12, 2017 at 13:33 Comment(5)
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?Dufresne
Yes you have to annotate your test with SpringBootTest to have fully Spring Boot up and running or create MethodValidationPostProcessor in your testRyan
Ah great!!! Thank you! After fiddling around with the MethodValidationPostProcessor I got it working!!!Dufresne
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)Hemimorphic
This is the ONLY combination, that worked for me. Thank you!! (why is this so complicated an error-prone in spring? It just should bei @Validated(..) and that's all).Hickerson
D
3

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());
Designate answered 26/2, 2019 at 9:26 Comment(1)
The answer of @SimonMartinelli was the correct one. Actually the position of @Validated(OnCreate.class) was just wrong.Dufresne
D
0

In my case, I wanna spy validator object, and test the validate().

Rely on @Rammgarot's answer, In order to clarify the problem more clearly, let's say we have: FooService.java

 private final Validator validator;

 public void validateRequestForExample(Request request) {
   validator.validate(request);
 }

and your FooServiceTest.java

@InjectMocks FooService fooservice;

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

void testValidatorRequestHandling() {
  fooService.validateRequestForExample(yourStubRequest);
}
Desecrate answered 28/6 at 9:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.