Hi,
I've got simple RestController with two endpoints, one POST on / for a single entity and one POST on /bulk for a list of entities. Both are annotated with @Valid, and the RestController is annotated @Validated.
All works fine and throws errors when missing @NotBlank entity fields, but when writing unit tests with WebTestClient I found out that the @Valid on the List of entities is ignored. Testing the single entity POST properly throws a 400 though.
Here's the controller:
@Validated
@RestController
@RequestMapping("/entities")
public class ApiController {
@PostMapping
public ResponseEntity save(@Valid @RequestBody Entity entity) {
//deal with valid entity
}
@PostMapping("/bulk")
@NotEmpty(message = "List is not allowed to be empty")
public ResponseEntity saveAll(@RequestBody @Valid List<Entity> entities) {
//deal with valid entities
}
}
This is the JUnit test method:
@Test
void testSaveAllShouldFailWhenRequiredFieldsAreMissing() {
WebTestClient.bindToController(apiController).build()
.post()
.uri("/entities/bulk")
.bodyValue(List.of(
Entity.builder().requiredField("dummy").build(),
Entity.builder().build()))
.exchange()
.expectStatus().isBadRequest();
}
The requiredField in the entity is annotated with @NotBlank.
I would expect this test to be succesfull (eg. the WebTestClient should receive a 400) but it doesn't. A similar test for the rest endpoint with a single entity works as expected.
Comment From: sbrannen
I've edited your comment to improve the formatting. You might want to check out this Mastering Markdown guide for future reference.
Comment From: sbrannen
How is the apiController used in your test created?
Do you instantiate it yourself?
Is it created by the ApplicationContext?
Comment From: sbrannen
Also, what happens if you rewrite your /bulk handler method as follows (i.e., with List<@Valid Entity>)?
@PostMapping("/bulk")
@NotEmpty(message = "List is not allowed to be empty")
public ResponseEntity saveAll(@RequestBody @Valid List<@Valid Entity> entities) {
//deal with valid entities
}
Comment From: gvermoen
Hi, thanks for your quick reply! My apiController is created by Mockito like this:
@ExtendWith(MockitoExtension.class)
class ApiControllerTest {
@Mock
private EntityService entityService;
@InjectMocks
private ApiController apiController;
I tried with your suggestion, it didn't help. Also I found out that the @NotEmpty annotation doesnt work as I expected, so I removed that one but that also doesn't help.
Comment From: sbrannen
Mockito will not apply a Spring proxy to honor the @Validated or @Valid annotations.
The needed proxy creation is performed by MethodValidationPostProcessor which you would need to register as a bean in the ApplicationContext. You would then need to acquire your controller as a bean from the same ApplicationContext. One way to do that would be to use the Spring TestContext Framework to load your test ApplicationContext and then have your proxied controller @Autowired into your test. For details, consult the Testing chapter of the Spring Framework reference manual.
For any further questions on this topic, please use Stack Overflow.
Comment From: gvermoen
OK thanks. I guess the fact that @Valid on a single entity actually works has put me on the wrong foot. Thanks for your directions.