I'm testing a controller:
#RestController()
public class MessagesController {
...
}
Using #WebMvcTest annotation:
#RunWith(SpringRunner.class)
#WebMvcTest(value = {MessagesController.class})
public class MessagesControllerTest {
private MockMvc mvc;
....
this.mvc.perform(
get("/messages/{id}", "dummyId")
.contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk());
...
But when I launch my test Spring tries to serialize an object of type List> and it fails:
Resolved Exception:
Type = org.springframework.http.converter.HttpMessageNotWritableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 500
Error message = null
Headers = {Content-Type=[application/json;charset=UTF-8]}
Content type = application/json;charset=UTF-8
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status
Expected :200
Actual :500
When I follow the execution in debug mode I found that the exception is thrown from : com.fasterxml.jackson.databind.ser.std.CollectionSerializer#serializeContents
And it's a JsonMappingException :
com.fasterxml.jackson.databind.JsonMappingException: Unwrapped property requires use of type information: can not serialize without disabling `SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS` (through reference chain: org.springframework.hateoas.Resource["content"]).
I also tried to inject an ObjectMapper into my context, but it's not used. Another ObjectMapper is used in the Serilaization process. This is the ObjectMapper I inject with #Import(HateoasConfiguration.class) on my test class:
#EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
#Configuration
public class HateoasConfiguration
{
private static final String SPRING_HATEOAS_OBJECT_MAPPER = "_halObjectMapper";
#Autowired
#Qualifier(SPRING_HATEOAS_OBJECT_MAPPER)
private ObjectMapper springHateoasObjectMapper;
#Bean(name = "objectMapper")
public ObjectMapper objectMapper() {
springHateoasObjectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
springHateoasObjectMapper.disable(SerializationFeature.FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS);
springHateoasObjectMapper.registerModules(
new ParameterNamesModule(),
new Jdk8Module(),
new JavaTimeModule(),
new URNModule()
);
return springHateoasObjectMapper;
}
}
Related
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("test#mail.com")
.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":"test#mail.com"}
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":"test#mail.com"}
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;
}
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("test#mail.com")
.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("test#mail.com")
.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
I think you are missing #WebMvcTest(controllers = Controller.class)
When I use #WebMvcTest to test my Spring Controller, I meet two questions.
MockMvc can't find right URL
After I add MockMvcBuilders.standaloneSetup(), mockMvc was worked.But My Controller throw NullPointException caused by #Autowired Service
I just do unit-test, so I don't want to use #SpringBootTest to starup all my SpringBoot Aplication.
I have try to #InjectMock My Controller and #Mock or #MockBean Service.The are all didn't work.
So could someone tell me why #Autowired mockMvc can't find the Controller and how to mock inner #Autowired filed in Spock unit test without #SpringBootTest?
Here are my controller
#RestController
#RequestMapping("/schedule/filter")
public class ScheduleObjectFilterController extends BaseController {
#PostMapping(path="/",produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String createScheduleObjectFilter(#RequestBody String body){
BaseResponse<ResponseStatus> resp = new BaseResponse<>(ResponseStatus._200);
try{
List<ScheduleObjectFilterParam> paramList = parseJSONArray(body,ScheduleObjectFilterParam.class);
resp.setStatus(scheduleObjectFilterService.createScheduleObjectFilter(paramList));
}catch(BaseRuntimeException e){
logger.error("Create ScheduleObjectFilter error:", e);
resp.setException(e);
}catch(Exception e){
logger.error("Create ScheduleObjectFilter error:", e);
resp.setStatus(ResponseStatus._500);
}
return renderJSON(resp);
}
#Autowired
private ScheduleObjectFilterService scheduleObjectFilterService;
}
Here are my Service and it's implement
public interface ScheduleObjectFilterService{
ResponseStatus createScheduleObjectFilter(List<ScheduleObjectFilterParam> paramList);
}
#Service
public class ScheduleObjectFilterServiceImpl extends BaseService implements ScheduleObjectFilterService {
public ResponseStatus createScheduleObjectFilter(List<ScheduleObjectFilterParam> paramList) {
// some code
}
}
Here is my test class
#WebMvcTest(controllers = [ScheduleObjectFilterController.class])
#ActiveProfiles("local")
class ScheduleSettingControllerSpecification extends Specification{
#Autowired
MockMvc mockMvc
#MockBean
BaseController baseController
#MockBean
ScheduleObjectFilterService scheduleObjectFilterService
def setup(){
//after add next line,the mockMvc can find url,but service throw NullPointException
//mockMvc = MockMvcBuilders.standaloneSetup(new ScheduleObjectFilterController()).build()
scheduleObjectFilterService.createScheduleObjectFilter(_ as List) >> ResponseStatus._500
}
def "MvcTest"(){
given:
ScheduleObjectFilterParam param = new ScheduleObjectFilterParam()
List<ScheduleObjectFilterParam> dataList = Collections.singletonList(param)
expect:
mockMvc.perform(post("/schedule/filter/").contentType(MediaType.APPLICATION_JSON).content(JSONObject.toJSONString(dataList)))
.andExpect(status().isOk())
}
}
before add MockMvcBuilders.standaloneSetup():
MockHttpServletRequest:
HTTP Method = POST
Request URI = /schedule/filter/
Parameters = {}
Headers = {Content-Type=[application/json]}
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = org.springframework.web.servlet.resource.ResourceHttpRequestHandler
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 404
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
After add That:
2019-11-10 11:53:22.896 [WEB] [ERROR] com.kaifa.hes.schedule.controller.ScheduleObjectFilterController.createScheduleObjectFilter() -> Create ScheduleObjectFilter error:
java.lang.NullPointerException: null
at com.kaifa.hes.schedule.controller.ScheduleObjectFilterController.createScheduleObjectFilter(ScheduleObjectFilterController.java:47) [classes/:?]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_201]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_201]
......
this exception direct to this line in controller
resp.setStatus(scheduleObjectFilterService.createScheduleObjectFilter(paramList));
#WebMvcTest is only used to test the web layer by application, so you need to mock any dependencies in controller and stub the method call. more information
#WebMvcTest(controllers = [ScheduleObjectFilterController.class])
#ActiveProfiles("local")
class ScheduleSettingControllerSpecification extends Specification{
#Autowired
MockMvc mockMvc
#MockBean
BaseController baseController
#MockBean
ScheduleObjectFilterService scheduleObjectFilterService
def "MvcTest"(){
when(this.scheduleObjectFilterService.createScheduleObjectFilter(ArgumentMatchers.anyList())
.thenReturn(// custom error);
given:
ScheduleObjectFilterParam param = new ScheduleObjectFilterParam()
List<ScheduleObjectFilterParam> dataList = Collections.singletonList(param)
expect:mockMvc.perform(post("/schedule/filter/").contentType(MediaType.APPLICATION_JSON).content(JSONObject.toJSONString(dataList)))
.andExpect(status().isOk())
}
}
#InjectMocks can't work in Spock. If we want to use the same function,here has a third-part extension
We can use the #Subject and #Collaborator annotation to solve this problem.
Pay attention,Spock has its own #Subject annotation.Before import the package,please check if it in correct packages
I am writing API test cases for one of my controllers, but it is resulting with a 404.
I thought it would be a typo but it is not. Below are the code snippets.
RestController: package: com.x.y.address.controller (src/main)
#RestController
public class AddressInternalController {
#PostMapping(value = "/v1/address-service/internal/company/address", produces = "application/json;charset=UTF-8")
#ResponseStatus(OK)
public #ResponseBody ResponseEntity<AddressModel> createCompanyAddress()
throws AddressException, BadRequestException {
return ok("SUCCESS");
}
}
My Test class: package com.x.y.address.controller (src/test)
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = TestApp.class, initializers = ConfigFileApplicationContextInitializer.class)
#WebMvcTest(controllers = AddressInternalController.class, secure = false)
public class AddressInternalControllerTest {
#Autowired
private MockMvc mvc;
#Before
public void init() {}
#Test
public void createAddressTest_when_invalid_company() throws Exception {
this.mvc.perform(post("/v1/address-service/internal/company/address").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
My app uses spring security and to bypass that I have created a TestAPP class so that it will help me build only the config without security.
TestApp: package com.x.y.address (src/test)
#EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class})
// #ComponentScan({"com.x.y.address.controller.AddressInternalController"})
public class TestApp {
}
Above are the structure of the class.
Initially I thought may be the program does not scan the controller package and hence the 404. Hence added the componentScan. But that did not help.
Searched through a lot of stack over flow but most of the 404 are due to a type but it is not in my case.
Error log:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /v1/address-service/internal/company/address
Parameters = {}
Headers = {Content-Type=[application/json]}
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = org.springframework.web.servlet.resource.ResourceHttpRequestHandler
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 404
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status
Expected :200
Actual :404
Any help shall be greatly appreciated.
I replaced:
#EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class})
// #ComponentScan({"com.x.y.address.controller.AddressInternalController"})
public class TestApp {
}
with:
#SpringBootApplication(exclude = { SecurityAutoConfiguration.class})
// #ComponentScan({"com.x.y.address.controller.AddressInternalController"})
public class TestApp {
}
and it worked.
UPDATE 1:
I noticed, in your #ComponentScan you use the path to the class itself, but you should point to the package with your controller. If you want to specify a class, use basePackageClasses property of #ComponentScan
I have a controller that should allow downloading files with arbitrary content type:
#GetMapping(value="/download/{directory}/{name}",
consumes=MediaType.ALL_VALUE)
#Timed
public ResponseEntity<byte[]> downloadFile(#PathVariable String directory,
#PathVariable String name) {
log.debug("REST request to download File : {}/{}", directory, name);
byte[] content = "it works".getBytes();
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_TYPE, "text/plain");
return new ResponseEntity<>(content, headers, HttpStatus.OK);
}
I want to test that in a unit test like this:
...
private MockMvc restFileMockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
final FileResource fileResource = new FileResource(fileService);
this.restFileMockMvc = MockMvcBuilders.standaloneSetup(fileResource)
.setCustomArgumentResolvers(pageableArgumentResolver)
.setControllerAdvice(exceptionTranslator)
.setConversionService(createFormattingConversionService())
.setMessageConverters(jacksonMessageConverter)
.setValidator(validator).build();
}
#Test
#Transactional
public void downloadFile() throws Exception {
String url = "/api/download/it/works.txt";
restFileMockMvc.perform(get(url).header(HttpHeaders.ACCEPT, "*/*"))
.andDo(MockMvcResultHandlers.print()) // Debugging only!
.andExpect(status().isOk());
}
But obviously, there is a problem with the content type, resp. the accept header. MockMvcResultHandlers.print() produces the following:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /api/download/DIRDIR/NAMENAME
Parameters = {}
Headers = {Accept=[*/*]}
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.example.storage.web.rest.FileResource
Method = public org.springframework.http.ResponseEntity<byte[]> com.example.storage.web.rest.FileResource.downloadFile(java.lang.String,java.lang.String)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.HttpMediaTypeNotAcceptableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 406
Error message = null
Headers = {Content-Type=[application/problem+json]}
Content type = application/problem+json
Body = {"type":"https://www.jhipster.tech/problem/problem-with-message","title":"Not Acceptable","status":406,"detail":"Could not find acceptable representation","path":"/api/download/DIRDIR/NAMENAME","message":"error.http.406"}
Forwarded URL = null
Redirected URL = null
Cookies = []
It looks like the request is sent with Accept: */*. What does Spring complain about then?
It could be an issue with your message converter, used in your test case. I too faced similar issue and resolved it by passing additional parameter in messageConverter for my mockMvc
this.restMockMvc = MockMvcBuilders.standaloneSetup(testResource)
.setCustomArgumentResolvers(pageableArgumentResolver)
.setControllerAdvice(exceptionTranslator)
.setMessageConverters(jacksonMessageConverter,new
ByteArrayHttpMessageConverter()).build();
You need to overload message converter property for MockMVC. for more info , relevant question
I was already using #SpringJUnitWebConfig(...) and included the #EnableWebMvc annotation to my imported Config. This seemed to add all the necessary converters. E.g.
#SpringJUnitWebConfig(MyTestConfig.class)
class MyTest {
#Inject
private WebApplicationContext wac;
private MockMvc mockMvc;
...
}
#EnableWebMvc
class MyTestConfig {
#Bean
...
}
I am trying to test the Controller of a very simple GET request with #WebMvcTest but I'm getting 404 instead of 200 and the console does not give me anything useful to understand what is going on.
I've put a breakpoint at the beginning of the controller but it never arrives. If I run the application, the endpoint works as expected.
Here's my controller:
#RestController
public class RegistroClienteController {
#GetMapping("/api/registro-cliente")
public ResponseEntity<Void> crearCliente() {
return new ResponseEntity<Void>(HttpStatus.OK);
}
}
And here's my test:
#RunWith(SpringRunner.class)
#WebMvcTest(RegistroClienteController.class)
#ContextConfiguration(classes = { SecurityConfig.class })
public class RegistroClienteControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
UserDetailsService userDetailsService;
#Test
public void test() throws Exception {
//#formatter:off
mockMvc
.perform(get("/api/registro-cliente"))
.andExpect(status().isOk());
//#formatter:on
}
}
And console's output:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /api/registro-cliente
Parameters = {}
Headers = {}
Handler:
Type = org.springframework.web.servlet.resource.ResourceHttpRequestHandler
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 404
Error message = null
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 = [[Cookie#624b3544 name = 'XSRF-TOKEN', value = 'f9d63654-4e21-4d41-b3bb-6767703268b5', comment = [null], domain = [null], maxAge = -1, path = '/', secure = false, version = 0, httpOnly = false]]
I was having the same error and after hours of searching found that the error is due to controller not being registered. The problem is described here.
Apparently, the following is not enough.
#WebMvcTest(controllers = {<ControllerToTest>.class})
You need to do,
#WebMvcTest(controllers = {<ControllerToTest>.class})
#Import(<ControllerToTest>.class)
I can try to change the annotations
#WebMvcTest(RegistroClienteController.class)
to
#SpringBootTest(classes = {your application class}.class)