Search code examples
spring-mvcspring-bootjunitmockitospring-mvc-test

SpringWebMvcTest - Test Requestbody using @Valid and custom validation


I am trying to test my controller endpoint and my requestbody annotated with @Valid annotation. My Testclass looks like the follow:

@RunWith(SpringRunner.class)
@WebMvcTest(value = BalanceInquiryController.class, secure = false)
public class BalanceInquiryControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private BalanceInquiryController balanceInquiryController;

    @Test
    public void testGetBalanceInquiry() throws Exception {
        RequestBuilder requestBuilder = MockMvcRequestBuilders
                .post("/com/balanceInquiry")
                .accept(MediaType.APPLICATION_JSON)
                .content("{\"comGiftCard\":{\"cardNumber\":\"1234567890\",\"pinNumber\":\"0123\"},\"comMerchant\":\"MERCHANT1\"}")
                .contentType(MediaType.APPLICATION_JSON);

        MvcResult mvcResult = mockMvc.perform(requestBuilder).andReturn();
        MockHttpServletResponse response = mvcResult.getResponse();
        assertEquals(HttpStatus.OK.value(), response.getStatus());
    }
}

My Controller - @PostMapping looks like that:

@PostMapping(value = "/com/balanceInquiry")
public ResponseEntity<?> getBalanceInquiry(@Valid @RequestBody BalanceInquiryModel balanceInquiry, Errors errors) {
    if (errors.hasErrors()) {
        return new ResponseEntity<String>("Validation error", HttpStatus.BAD_REQUEST);
    }
    //do any stuff...
    return new ResponseEntity<BalanceInquiryResponse>(balanceInquiryResponse, HttpStatus.OK);
}

My BalanceInquiryModel is annotated with @Valid and has some hibernate and custom validations behind. Those validations are all ok and already unit tested.

What I like to test is my endpoint where I send a valid json request body expecting a 200 response and also an invalid json request body expecting a 400 response validated by the set @Valid implementation.

For example an unvalid call is to send no pinNumber or length < 4.

I have read some threads and some uses MockMvcBuilders.standaloneSetup() to mock the full controller. But I wont do a full integration test.

Not quite sure how to go on with this situation and if I should go on.

P.S.: At the moment I get always a 200 response no matter if the validation should give an error or not.

Here a gist for more code and the validation classes/models.


Solution

  • Here's one of my example I work on my project hope it help you out:

    I have a global exception handler to handler my MethodArgumentNotValidException and throw it

    @RequestMapping(value = "/add", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
        public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
    
            User savedUser = userService.save(user);
    
            return new ResponseEntity<User>(savedUser, HttpStatus.CREATED);
        }
    
    
    public void testAdduser() throws Exception{
            final User request = new User();
            request.setFirstName("Test");
            request.setLastName("some description");
    
            mockMvc.perform(post(END_POINT+"/add")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(stringify(request))
    
            ).andDo(print()).andExpect(status().isUnprocessableEntity())
            ;
        }
    private String stringify(Object object) throws JsonProcessingException {
            return new ObjectMapper().writeValueAsString(object);
        }
    

    Update:

    I think your main problem is that you are using @WebMvcTest in stead of @SpringBootTest.

    the different between 2 of them is that:

    @SpringBootTest annotation will loads complete application and injects all the beans which is can be slow.

    @WebMvcTest - for testing the controller layer. it doesn't inject other bean beside the @RestController

    so if you are just testing just pure controller to see u can reach the endpont then you can just use @WebMvcTest which will make your test run faster.

    but in your case, you want it to run the spring validation, you will need to use @SpringBootTest

    for detailed: https://spring.io/guides/gs/testing-web/