This is my Controller class.
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ValidController {
@GetMapping("/valid")
public ResponseEntity<String> valid(@RequestParam(required = false) @Valid @NotNull String firstName, @RequestParam(required = false) String lastName) {
System.out.println("firstName = " + firstName);
return ResponseEntity.ok(firstName);
}
}
and this is test code with MockMvc, @SpringBootTest
@SpringBootTest(classes = ValidController.class)
@AutoConfigureMockMvc
public class ValidControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
ServletModelAttributeMethodProcessor servletModelAttributeMethodProcessor;
@Test()
void validator_valid() throws Exception {
mockMvc.perform(get("/valid")
.param("lastName", "foo"))
.andExpect(status().is4xxClientError())
.andReturn();
}
}
So here is the problem. I know that I can test @Valid annotation with @SpringBootTest, but I don't want to simply add every beans that I made. All I want to do is testing ValidController without adding any other beans that I made. How Can I achieve it?
I tried
@Configuration
class TestConfig {
@Bean
public ServletModelAttributeMethodProcessor servletModelAttributeMethodProcessor() {
return new ServletModelAttributeMethodProcessor(true);
}
}
@SpringBootTest
(classes = {ValidController.class
, MethodValidationPostProcessor.class
, ValidationAutoConfiguration.class
, TestConfig.class
}
@AutoConfigureMockMvc
public class ValidControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
ServletModelAttributeMethodProcessor servletModelAttributeMethodProcessor;
@Test()
void validator_valid() throws Exception {
mockMvc.perform(get("/valid")
.param("lastName", "foo"))
.andExpect(status().is4xxClientError())
.andReturn();
}
}
@Validated //this is added
@RestController
public class ValidController {
@GetMapping("/valid")
public ResponseEntity<String> valid(@RequestParam(required = false) @Valid @NotNull String firstName, @RequestParam(required = false) String lastName) {
return ResponseEntity.ok(firstName);
}
}
in this case, test throw ConstraintViolationException. But I don't want to add @Validate at Controller class which is unnecessary in operation. How can I check @Valid in Controller without using @Validated?
That is what @WebMvcTest
is for. It will bootstrap only the MVC parts needed and your controller, if you have any dependencies use @MockBean
to mock those.
Some other tips are don't use parameters use an object/dto for binding and validation. Makes it a lot easier.
Don't use @Validated
at the controller level that serves a different purpose and will lead to different error handling. Spring Framework 6.2 includes better support for MVC and annotated parameters.
public class FormDto {
@NotNull
private String firstName;
@NotNull
private String lastName;
// Getters + Setters omitted
}
@RestController
public class ValidController {
@GetMapping("/valid")
public ResponseEntity<String> valid(@Valid FormDto form) {
System.out.println("firstName = " + form.getFirstName());
return ResponseEntity.ok(form.getFirstName());
}
}
The test
@WebMvcTest(ValidController.class)
public class ValidControllerTest {
@Autowired
private MockMvc mockMvc;
@Test()
void validator_valid() throws Exception {
mockMvc.perform(get("/valid")
.param("lastName", "foo"))
.andExpect(status().is4xxClientError())
.andReturn();
}
}
That basically is what you need. Using a form object makes things easier for validation then simple @RequestParam
in your method.