Issue
I am testing request validation in Spring RestController with the help of integration testing and MockMvc.
ControllerTest.java
@ExtendWith(MockitoExtension.class)
@SpringBootTest(classes = Controller.class)
@AutoConfigureMockMvc(addFilters = false)
class ControllerTest {
private ObjectMapper objectMapper;
@MockBean
private Service service;
@Autowired
private MockMvc mockMvc;
@BeforeEach
void setUp() {
objectMapper = new ObjectMapper();
}
@Test
void createOrAdd_shouldReturnErrorResponseOnInvalidInput() throws Exception {
Request request = Request.builder()
.name("name<script>")
.primaryEmail("[email protected]")
.build();
mockMvc.perform(MockMvcRequestBuilders.post("/api/create")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(request))
.characterEncoding("utf-8"))
.andExpect(MockMvcResultMatchers.status().isBadRequest());
}
}
Controller.java :
@Slf4j
@RestController
public class Controller {
private final Service service;
public Controller(Service service) {
this.service = service;
}
@PostMapping(value = "/api/create", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<GenericResponse<Response>> createOrAdd(@RequestBody @Valid Request request, Errors errors) {
GenericResponse<Response> genericResponse = new GenericResponse<>();
try {
if (errors.hasErrors()) {
throw new RequestParamsException(errors.getAllErrors());
}
Response response = service.createOrAdd(request);
genericResponse.setData(response);
return ResponseEntity.ok().body(genericResponse);
} catch (RequestParamsException ex) {
genericResponse.setErrors(ex.getErrors());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(genericResponse);
}
}
Error :
WARN 17304 --- [ main] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=utf-8' not supported]
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/create
Parameters = {}
Headers = [Content-Type:"application/json;charset=utf-8", Accept:"application/json", Content-Length:"162"]
Body = {"name":"name<script>alert(1)</script>","primary_email_address":"[email protected]"}
Session Attrs = {}
Handler:
Type = com.org.controller.Controller
Method = com.org.controller.Controller#createOrAdd(Request, Errors)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.HttpMediaTypeNotSupportedException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 415
Error message = null
Headers = [Accept:"application/octet-stream, text/plain, application/xml, text/xml, application/x-www-form-urlencoded, application/*+xml, multipart/form-data, multipart/mixed, */*"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status expected:<400> but was:<415>
Expected :400
Actual :415
I have used the correct Content-Type
and Accept
Headers while making a call in Test.java
using mockMvc
, but still it's giving HttpMediaTypeNotSupportedException. Tried many combinations in Accept
and Content-Type
but still not working.
I have read many SO questions related to this exception, but couldn't find what's the issue here. Still not able to figure out why it's saying HttpMediaTypeNotSupportedException.
Update : After removing addFilters = false
as suggested, not able to find the handler itself.
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/create
Parameters = {}
Headers = [Content-Type:"application/json;charset=utf-8", Accept:"application/json", Content-Length:"162"]
Body = {"name":"name<script>alert(1)</script>","primary_email_address":"[email protected]"}
Session Attrs = {org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN=org.springframework.security.web.csrf.DefaultCsrfToken@7a687d8d}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 403
Error message = Forbidden
Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status expected:<400> but was:<403>
Expected :400
Actual :403
Request :
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CreateAgencyRequest {
@NotNull(message = "name can't be null")
@JsonProperty(value = "name")
@Pattern(regexp = REGEX_CONST, message = "name is not valid")
private String name;
@NotNull(message = "primary_email_address can't be null")
@JsonProperty(value = "primary_email_address")
private String primaryEmail;
}
Solution
Lets take a look at your test.
@WebMvcTest(classes = Controller.class)
@AutoConfigureMockMvc(addFilters = false)
class ControllerTest {
private ObjectMapper objectMapper;
@MockBean
private Service service;
@Autowired
private MockMvc mockMvc;
@Test
void createOrAdd_shouldReturnErrorResponseOnInvalidInput() throws Exception {
Request request = Request.builder()
.name("name<script>")
.primaryEmail("[email protected]")
.build();
mockMvc.perform(MockMvcRequestBuilders.post("/api/create")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(request))
.characterEncoding("utf-8"))
.andExpect(MockMvcResultMatchers.status().isBadRequest());
}
}
First the @ExtendWith(MockitoExtension.class)
doesn't add anything as you are using, correctly, the @MockBean
annotation which is handled by Spring. So you should remove that.
Next the @SpringBootTest
is for bootstrapping the full application to run an integration test. What you want is a sliced test for the web, so instead of @SpringBootTest
use @WebMvcTest
this will make your test considerably faster. You can then also remove @AutoConfigureMockMvc
as that is added by default.
You disabled all filters with @AutoConfigureMockMvc(addFilters = false)
probably due to the 403 you got. The 403 you have is the result of enabling CSRF (enabled by default) and not adding that to the request. If you don't want CSRF (you probably want it) either disable that in the Security configuration, or if you want it modify your request.
Looking at the error you have the culprit is the characterEncoding
being added to the content type, so you probably want/should remove that.
With all that your test should look something like this.
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
@WebMvcTest(Controller.class)
class ControllerTest {
private ObjectMapper objectMapper = new ObjectMapper();
@MockBean
private Service service;
@Autowired
private MockMvc mockMvc;
@Test
void createOrAdd_shouldReturnErrorResponseOnInvalidInput() throws Exception {
Request request = Request.builder()
.name("name<script>")
.primaryEmail("[email protected]")
.build();
mockMvc.perform(MockMvcRequestBuilders.post("/api/create")
.with(csrf()) // Add CSRF field for security
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(request))
.andExpect(MockMvcResultMatchers.status().isBadRequest());
}
}
NOTE: If you also have authentication in place you might also need to add a user/password to the request as explained here
Answered By - M. Deinum
Answer Checked By - Timothy Miller (JavaFixing Admin)