Spring integration testing a controller with dependency injection - java

I am trying ot write an integration test for one of my controller classes which have an injected dependency in it. I try to test the part of my controller where it's calling a method through the injected object, but when i run my test its failing due to a null pointer exception. At the test i used #ContexConfiguration and #RunsWith annotations, but it didin't helped.
Some code might help :)
AuthenticationController:
#Controller
public class AuthenticationController {
#Resource(name = "userManagement")
private UserManagement um;
#RequestMapping(method = RequestMethod.POST)
public String onSubmit(#ModelAttribute("user") UserForm user,
BindingResult result, Model model, HttpSession session) {
LoginFormValidator validator = new LoginFormValidator();
validator.validate(user, result);
if (result.hasErrors()) {
return "login";
} else {
User u = um.login(user.getEmail(), user.getPwd());
if (u != null) {
session.setAttribute("user", u);
LOGGER.info("succesful login with email: " + u.getEmail());
model.addAttribute("result", "succesful login");
} else {
model.addAttribute("result", "login failed");
}
return "result";
}
}
in test-context.xml:
beans:bean id="userManagement" class="my.packages.UserManagement"
AuthenticationControllerTest:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"test-context.xml" })
public class AuthenticationControllerTest {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private AuthenticationController controller;
#Before
public void setUp() {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
controller = new AuthenticationController();
}
#Test
public void testLoginPost() throws Exception {
request.setMethod("POST");
request.setRequestURI("/login");
request.setParameter("email", "test#email.com");
request.setParameter("pwd", "test");
final ModelAndView mav = new AnnotationMethodHandlerAdapter()
.handle(request, response, controller);
final UserForm u =
assertAndReturnModelAttributeOfType(mav, "user", UserForm.class);
assertEquals("test#email.com", u.getEmail());
assertEquals("test", u.getPwd());
assertViewName(mav, "result");
/* if UserForm is not valid */
final BindingResult errors = assertAndReturnModelAttributeOfType(mav,
"org.springframework.validation.BindingResult.user",
BindingResult.class);
assertTrue(errors.hasErrors());
assertViewName(mav, "login");
}
The stacktrace tells me that the error happens where the test calls the login method of the injected userMangaement object. um = null so the injection is not working with the test.
The controller works fine in useage.
Any comment would help a lot!
Thanks in advance,
Sorex

If you want autowire dependencies you can't create your controller like this:
controller = new AuthenticationController();
You can autowire you dependency into your test
#Autowired
private UserManagement um;
and create constructor in your controller to be able to do this:
#Before
public void setUp() {
controller = new AuthenticationController(um);
}
But I would recommend to use MockServletContext.
MockServletContext mockServletContext = new MockServletContext();
mockServletContext.addInitParameter("contextConfigLocation", "path to your xml config"));
ContextLoaderListener listener = new ContextLoaderListener();
listener.initWebApplicationContext(mockServletContext);
There should be also reference to DispatcherServlet somewhere. I have never done this in servlet environmentm, only in spring portlet mvc, but it shoul be similar. The idea is to create fake web application context and call dispacher servlet to have full integration test between your controllers an spring configuration.

Related

SpringBootTest with MockMvcBuilders stand alone setup is not loading my ControllerAdvice despite setting it

I am creating my controller and controller advice like this:
Test class:
#RunWith(SpringRunner.class)
#SpringBootTest
public class TestController {
private MockMvc mockMvc;
#Mock
private MyService myService;
#Autowired
#InjectMocks
private MyController myController;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
//Build the controller mock handler
mockMvc = MockMvcBuilders
.standaloneSetup(MyController.class)
.setControllerAdvice(new MyControllerAdvice())
//This also doesn't work
//.setHandlerExceptionResolvers(createExceptionResolver())
.build();
}
//This also did not work
private ExceptionHandlerExceptionResolver createExceptionResolver() {
ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver() {
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
Method method = new ExceptionHandlerMethodResolver(MyControllerAdvice.class).resolveMethod(exception);
return new ServletInvocableHandlerMethod(new MyControllerAdvice(), method);
}
};
exceptionResolver.afterPropertiesSet();
return exceptionResolver;
}
/**
* Tests passing bad input to see if our exception handler is called.
*/
#Test
public void testBadRequest()
{
//Make a request object that has a bad input (e.g. bad date string)
MyRequest request = new MyRequest();
//Set the request values
request.setDate( "a" );
try
{
myController.getSomething( request );
}
catch (Exception e)
{
//It reaches here without ever reaching my controller advice in debugging
e.printStackTrace();
}
}
}
Controller advice:
#EnableWebMvc
#ControllerAdvice
#Component
public class MyControllerAdvice {
#ExceptionHandler(value = Exception.class)
public ResponseEntity<String> handleException(HttpServletRequest request, Exception exception) throws Exception
{
//This is never called (I'm using a debugger and have a breakpoint here)
return new ResponseEntity<String>(
"test",
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
There are two issues in your example:
MockMvcBuilders#standaloneSetup() receives Controller objects as parameters, not the Class objects. So it should be:
mockMvc = MockMvcBuilders
.standaloneSetup(new MyController())
.setControllerAdvice(new MyControllerAdvice())
.build();
You are calling myController.getSomething( request ) directly, while you should use previously built mockMvc. Direct call is unadvised as it's not processed with TestDispatcherServlet. Here is a couple of examples for mockMvc requests:
GET
mockMvc.perform(get("/testSomething"))
.andExpect(status().is5xxServerError())
.andReturn();
POST
mockMvc.perform(post("/testSomething")
.contentType(MediaType.APPLICATION_JSON)
.content(json)) //it's JSON string
.andExpect(status().is5xxServerError())
.andReturn();

How to access resource mapped with #RequestMapping with #RestController

I am working on the Spring Boot web application. I am running two different application one for service (Rest Resource) and other one is UI which show the request on HTML page on the bases of response got on the rest request.
My all rest services are created by
#Component
#Api(value = "/api/1/registration", description = "Access and manage your information")
#Path("/api/1/registration")
#Produces(MediaType.APPLICATION_JSON)
#Slf4j
public class RegistrationResource {
...
...
#ApiOperation(value = "Find user by id", notes = "Return a user by id", response = Registration.class)
#Path("/{id}")
#GET
#Timed
#Override
public Registration get(#PathParam("id") String id) {
// my logic
}
}
Restangular Request
Restangular.one("registration/1").get().then(function (data) {
...
},function(error) {
...
});
When I do restangular request from ui, its working fine. Now I need to have a servlet resource. For that I create new resource class
#Slf4j
#RestController
#RequestMapping("/api/1/test")
public class DownloadResource{
#RequestMapping(value = "/downloadtesting", method = RequestMethod.GET)
public void download(HttpServletResponse response, HttpServletRequest request){
// I need to call this method... How can I
}
}
FYI: All my resources are registered as following...
Reflections reflections = new Reflections("com.sample.resource");
Set<Class<? extends Object>> resources =
reflections.getTypesAnnotatedWith(Path.class); //
resources.forEach(r -> {
log.debug("Registering resource " + r);
register(beanFactory.getBean(r));
});
// Same I did for RequestMapping.class also but I am getting 404 error for downloadtesting api.
NOTE: If I try with following version for downloadtesting in RegistrationResource then I am getting HttpServletRequest / Response null.
public class RegistrationResource{
#Path("/downloadtesting")
public void download(HttpServletResponse response, HttpServletRequest request){
// I need to call this method... How can I
}
}
Can any one help me?

Testing REST endpoints with custom exception handling

I am working on a project with Spring microservices (modules) and I want to test my REST endpoint using MockMvc. My testing works fine for cases where the request is valid but it is not working when requesting a url that is invalid. By not working I mean my custom exception handler (#ControllerAdvice) does not get called, the exception gets thrown and the test fails.
My exception handler and testing class are implemented in different modules.
common-module (ExceptionHandler)
#ControllerAdvice
public class CoreExceptionHandler {
#ExceptionHandler(value = Exception.class)
public ResponseEntity<ErrorMessageDTO> handleException(Exception ex, HttpServletRequest request) {
// Getting servlet request URL
String uri = request.getRequestURI();
HttpStatus a;
ErrorMessageDTO errorMessage;
if (ex instanceof CoreException) {
CoreException e = (CoreException) ex;
...
errorMessage = new ErrorMessageDTO(e, uri);
} else {
errorMessage = new ErrorMessageDTO(ex, uri);
...
}
return new ResponseEntity<ErrorMessageDTO>(errorMessage, a);
}
}
country-module
This is where my REST endpoint and Testing class are implemented. The common module dependency is included in this module's pom.xml and the packages are scanned through the main class.
CountryApplication.java
#EnableCaching
#EnableDiscoveryClient
#EnableAspectJAutoProxy
#SpringBootApplication(scanBasePackages = {
"com.something1.something2.something3.common.exception",
"com.something1.something2.something3.common.util.logged",
"com.something1.something2.something3.country"
})
public class CountryApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(CountryApplication.class, args);
}
...
}
CountryService.java
This is a method in my Service class.
#GetMapping("/{id:\\d+}")
public CountryDTO getCountryById(#PathVariable("id") Integer id) throws CoreException {
Country countryEntity = this.countryRepository.findOne(id);
// requesting for id that does not exist
if (countryEntity == null) {
throw new CoreException(CoreError.ENTITY_NOT_FOUND);
}
return this.countryMapper.daoToDto(countryEntity);
}
CountryServiceTest.java
#SpringBootTest
#AutoConfigureMockMvc
#AutoConfigureTestDatabase
#RunWith(SpringRunner.class)
public class CountryServiceTest {
...
#Autowired
private MockMvc mockMvc;
#Test
public void getByIdTest() throws Exception {
// Get by id exists
mockMvc.perform(get("/2"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andDo(print());
// Get by id not exists. NOT WORKING
mockMvc.perform(get("/100000"))
.andExpect(status().isNotFound())
.andExpect(content().contentType(contentType));
}
}
As I described above, the problem is that at the second request of the test method, the CoreExceptionHandler does not get called and the test fails throwing a:
NestedServletException: Request processing failed; nested exception is com.something1.something2.something3.common.exception.CoreException.
The dependency for the common module is well configured (at least when I am deploying in non-test mode) since I am using it for other things too, plus the ExceptionHandler gets called when I am not testing.
Another strange thing is that when I am deploying my Test, Spring Boot's logs show that the CoreExceptionHandler gets detected. This is the line. Detected #ExceptionHandler methods in coreExceptionHandler
There are two problems as explained below:
(1) ControllerAdvice not being set for MockMvc object in your CountryServiceTest class, which can be done as shown below:
MockMvc mockMvc = standaloneSetup(yourController)
.setHandlerExceptionResolvers(new CoreExceptionHandler())
.build();
(2) Because CoreException is wrapper by NestedServletException by the Spring Container, you need to use exception.getCause() to check your exception as shown below:
#ControllerAdvice
public class CoreExceptionHandler {
#ExceptionHandler(value = Exception.class)
public ResponseEntity<ErrorMessageDTO> handleException(Exception ex,
HttpServletRequest request) {
// Getting servlet request URL
String uri = request.getRequestURI();
HttpStatus a;
ErrorMessageDTO errorMessage;
//check with exception cause
if (ex.getCause() instanceof CoreException) {
CoreException e = (CoreException) ex;
...
errorMessage = new ErrorMessageDTO(e, uri);
} else if (ex instanceof CoreException) {
//this block will be used only when direct CoreException triggered
CoreException e = (CoreException) ex;
...
errorMessage = new ErrorMessageDTO(e, uri);
} else {
errorMessage = new ErrorMessageDTO(ex, uri);
...
}
return new ResponseEntity<ErrorMessageDTO>(errorMessage, a);
}
}
Also, I suggest not to handle all exception types in a single generic method, which will be very hard to support/maintain, rather split your CoreExceptionHandler using multiple #ExceptionHandler methods/classes.

Login page Junit test with mockito

I guess this is simple problem, but I am unable to get my head around it. this is the class for which I need to write test case
#Controller
#SessionAttributes
public class LoginController {
#RequestMapping(value = "/Login", method = RequestMethod.GET)
public ModelAndView displayLogin(#RequestParam(value = "error", required = false) String error,
#RequestParam(value = "logout", required = false) String logout,
HttpServletRequest request,
HttpServletResponse response) {
ModelAndView modelForLogin = new ModelAndView();
if (error != null) {
// Include login failure message
modelForLogin.addObject("loginFailure", "Invalid username and password!");
}
if ("user".equals(logout)) {
// Include logout message
modelForLogin.addObject("msg", "You've been logged out successfully.");
}
else {
modelForLogin.addObject("msg","");
}
modelForLogin.setViewName("Login");
return modelForLogin;
}
}
This is what I have got till now...
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration({ " servlet-xml "})
public class LoginControllerTest {
#Mock HttpServletRequest request;
#Mock HttpServletResponse response;
#Mock HttpSession session;
private MockMvc mockMvc;
#Before
protected void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
ModelAndView modelForLogin = mockito.mock(ModelAndView.class);
mockito.when(modelForLogin.error()).thenReturn("error");
mockito.when(modelForLogin.logout()).thenReturn("logout");
}
#Test
public void TestLoginError() throws Exception {
mockMvc.perform(get("/Login").param()).andExpect(status().isOk()).andExpect(model().attributeExists("msg"));
}
#Test
public void testLogin() throws Exception {
mockMvc.perform(get("/Login")).andExpect(status().isOk());
mockMvc.perform(get("/Login").param("logout", "log")).andExpect(status().isOk()).andExpect(model().attributeExists("msg"));
mockMvc.perform(get("/Login").param("error", "log")).andExpect(status().isOk()).andExpect(model().attributeExists("error"));
mockMvc.perform(get("/Login").param("logout", "log").param("error", "log")).andExpect(status().isOk()).andExpect(model().attributeExists("msg")).andExpect(model().attributeExists("error"));
mockMvc.perform(get("/Login")).andExpect(status().isOk()).andExpect(view().name("login"));
}
}
Can anyone please let me know proper way to write test case for this?
Given the code you have, there is no need to mock. A sample test case would look like below:
ModelAndView mvw = displayLogin("error", null, null, null);
assertEquals("Invalid username and password!", mvw.getModelMap().get("loginFailure"));

Why is my controller sending the content type "application/octet-stream"?

I have a REST controller:
#RequestMapping(value = "greeting", method = RequestMethod.GET, produces = "application/json; charset=utf-8")
#Transactional(readOnly = true)
#ResponseBody
public HttpEntity<GreetingResource> greetingResource(#RequestParam(value = "message", required = false, defaultValue = "World") String message) {
GreetingResource greetingResource = new GreetingResource(String.format(TEMPLATE, message));
greetingResource.add(linkTo(methodOn(AdminController.class).greetingResource(message)).withSelfRel());
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.add("Content-Type", "application/json; charset=utf-8");
return new ResponseEntity<GreetingResource>(greetingResource, responseHeaders, HttpStatus.OK);
}
As you can see, I'm trying hard to specify the content type returned by the controller.
It is accessed with a REST client:
public String getGreetingMessage() {
String message;
try {
HttpHeaders httpHeaders = Common.createAuthenticationHeaders("stephane" + ":" + "mypassword");
ResponseEntity<GreetingResource> responseEntity = restTemplate.getForEntity("/admin/greeting", GreetingResource.class, httpHeaders);
GreetingResource greetingResource = responseEntity.getBody();
message = greetingResource.getMessage();
} catch (HttpMessageNotReadableException e) {
message = "The GET request FAILED with the message being not readable: " + e.getMessage();
} catch (HttpStatusCodeException e) {
message = "The GET request FAILED with the HttpStatusCode: " + e.getStatusCode() + "|" + e.getStatusText();
} catch (RuntimeException e) {
message = "The GET request FAILED " + ExceptionUtils.getFullStackTrace(e);
}
return message;
}
The http headers are created by a utility:
static public HttpHeaders createAuthenticationHeaders(String usernamePassword) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
byte[] encodedAuthorisation = Base64.encode(usernamePassword.getBytes());
headers.add("Authorization", "Basic " + new String(encodedAuthorisation));
return headers;
}
The web security configuration and code work fine. I make sure of this using a mockMvc based integration test which succeeds.
The only test that fails is the one based on the REST template:
#Test
public void testGreeting() throws Exception {
mockServer.expect(requestTo("/admin/greeting")).andExpect(method(HttpMethod.GET)).andRespond(withStatus(HttpStatus.OK));
String message = adminRestClient.getGreetingMessage();
mockServer.verify();
assertThat(message, allOf(containsString("Hello"), containsString("World")));
}
The exception given in the Maven build console output is:
java.lang.AssertionError:
Expected: (a string containing "Hello" and a string containing "World")
got: "The GET request FAILED org.springframework.web.client.RestClientException : Could not extract response: no suitable HttpMessageConverter found for response type [class com.thalasoft.learnintouch.rest.resource.GreetingR esource] and content type [application/octet-stream]\n\tat org.springframework.web.client.HttpMessageConverte rExtractor.extractData(HttpMessageConverterExtract or.java:107)
I'm using the Spring Framework 3.2.2.RELEASE version and the Spring Security 3.1.4.RELEASE version on the Java 1.6 version.
At first, I had a bare bone REST template:
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
return restTemplate;
}
I have now added to it, hoping it would help:
private static final Charset UTF8 = Charset.forName("UTF-8");
#Bean
public RestTemplate restTemplate() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("application", "json", UTF8)));
messageConverters.add(mappingJackson2HttpMessageConverter);
Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setClassesToBeBound(new Class[] {
GreetingResource.class
});
MarshallingHttpMessageConverter marshallingHttpMessageConverter = new MarshallingHttpMessageConverter(jaxb2Marshaller, jaxb2Marshaller);
messageConverters.add(marshallingHttpMessageConverter);
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new FormHttpMessageConverter());
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("text", "plain", UTF8)));
messageConverters.add(stringHttpMessageConverter);
messageConverters.add(new BufferedImageHttpMessageConverter());
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}
But it didn't change anything and the exception remains the same.
My understanding is that, it is not the REST template that needs any specific JSON configuration, but rather, that, for some reason, my controller is spitting out some application/octet-stream content type instead of some application/json content type.
Any clue?
Some additional information...
The admin rest client bean in the web test configuration:
#Configuration
public class WebTestConfiguration {
#Bean
public AdminRestClient adminRestClient() {
return new AdminRestClient();
}
#Bean
public RestTemplate restTemplate() {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("application", "json", UTF8)));
messageConverters.add(mappingJackson2HttpMessageConverter);
Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setClassesToBeBound(new Class[] {
Greeting.class
});
MarshallingHttpMessageConverter marshallingHttpMessageConverter = new MarshallingHttpMessageConverter(jaxb2Marshaller, jaxb2Marshaller);
messageConverters.add(marshallingHttpMessageConverter);
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new FormHttpMessageConverter());
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("text", "plain", UTF8)));
messageConverters.add(stringHttpMessageConverter);
messageConverters.add(new BufferedImageHttpMessageConverter());
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}
}
The base test class:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration( classes = { ApplicationConfiguration.class, WebSecurityConfig.class, WebConfiguration.class, WebTestConfiguration.class })
#Transactional
public abstract class AbstractControllerTest {
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Autowired
protected RestTemplate restTemplate;
protected MockRestServiceServer mockServer;
#Before
public void setup() {
this.mockServer = MockRestServiceServer.createServer(restTemplate);
}
}
The web init class:
public class WebInit implements WebApplicationInitializer {
private static Logger logger = LoggerFactory.getLogger(WebInit.class);
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerListener(servletContext);
registerDispatcherServlet(servletContext);
registerJspServlet(servletContext);
createSecurityFilter(servletContext);
}
private void registerListener(ServletContext servletContext) {
// Create the root application context
AnnotationConfigWebApplicationContext appContext = createContext(ApplicationConfiguration.class, WebSecurityConfig.class);
// Set the application display name
appContext.setDisplayName("LearnInTouch");
// Create the Spring Container shared by all servlets and filters
servletContext.addListener(new ContextLoaderListener(appContext));
}
private void registerDispatcherServlet(ServletContext servletContext) {
AnnotationConfigWebApplicationContext webApplicationContext = createContext(WebConfiguration.class);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(webApplicationContext));
dispatcher.setLoadOnStartup(1);
Set<String> mappingConflicts = dispatcher.addMapping("/");
if (!mappingConflicts.isEmpty()) {
for (String mappingConflict : mappingConflicts) {
logger.error("Mapping conflict: " + mappingConflict);
}
throw new IllegalStateException(
"The servlet cannot be mapped to '/'");
}
}
private void registerJspServlet(ServletContext servletContext) {
}
private AnnotationConfigWebApplicationContext createContext(final Class... modules) {
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(modules);
return appContext;
}
private void createSecurityFilter(ServletContext servletContext) {
FilterRegistration.Dynamic springSecurityFilterChain = servletContext.addFilter("springSecurityFilterChain", DelegatingFilterProxy.class);
springSecurityFilterChain.addMappingForUrlPatterns(null, false, "/*");
}
}
The web configuration:
#Configuration
#EnableWebMvc
#EnableEntityLinks
#ComponentScan(basePackages = "com.thalasoft.learnintouch.rest.controller")
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
PageableArgumentResolver resolver = new PageableArgumentResolver();
resolver.setFallbackPageable(new PageRequest(1, 10));
resolvers.add(new ServletWebArgumentResolverAdapter(resolver));
super.addArgumentResolvers(resolvers);
}
}
The application configuration is empty for now:
#Configuration
#Import({ ApplicationContext.class })
public class ApplicationConfiguration extends WebMvcConfigurerAdapter {
// Declare "application" scope beans here, that is, beans that are not only used by the web context
}
I had my doubts before, but now that you've posted everything, here's what's up. Assuming the RestTemplate object you use in your getGreetingMessage() method is the same as the one declared in the #Bean method, the problem starts here
this.mockServer = MockRestServiceServer.createServer(restTemplate);
This call overwrites the default ClientHttpRequestFactory object that the RestTemplate object uses internally with a mock. In your getGreetingMessage() method, this call
ResponseEntity<GreetingResource> responseEntity = restTemplate.getForEntity("/admin/greeting", GreetingResource.class, httpHeaders);
doesn't actually go through the network. The RestTemplate uses the mocked ClientHttpRequestFactory to create a fake ClientHttpRequest which produces a fake ClientHttpResponse which doesn't have a Content-Type header. When the RestTemplate looks at the ClientHttpResponse to determine its Content-Type and doesn't find one, it assumes application/octet-stream by default.
So, your controller isn't setting the content type because your controller is never hit. The RestTemplate is using a default content type for your response because it is mocked and doesn't actually contain one.
From your comments:
I wonder if I understand what the mock server is testing. I understand
it is to be used in acceptance testing scenario. Is it supposed to hit
the controller at all ?
The javadoc for MockRestServiceServer states:
Main entry point for client-side REST testing. Used for tests that
involve direct or indirect (through client code)
use of the RestTemplate. Provides a way to set up fine-grained
expectations on the requests that will be performed through the
RestTemplate and a way to define the responses to send back removing
the need for an actual running server.
In other words, it's as if your application server didn't exist. So you could throw any expectations (and actual return values) you wanted and test whatever happens from the client side. So you aren't testing your server, you are testing your client.
Are you sure you aren't looking for MockMvc, which is
Main entry point for server-side Spring MVC test support.
which you can setup to actually use your #Controller beans in an integration environment. You aren't actually sending HTTP request, but the MockMvc is simulating how they would be sent and how your server would respond.
It is bug in MockHttpServletRequest and I will try to describe it.
Issue in tracker https://jira.springsource.org/browse/SPR-11308#comment-97327
Fixed in version 4.0.1
Bug
When DispatcherServlet looking for method to invoke it using some RequestConditions. One of them is ConsumesRequestCondition. The following is a piece of code:
#Override
protected boolean matchMediaType(HttpServletRequest request) throws HttpMediaTypeNotSupportedException {
try {
MediaType contentType = StringUtils.hasLength(request.getContentType()) ?
MediaType.parseMediaType(request.getContentType()) :
MediaType.APPLICATION_OCTET_STREAM;
return getMediaType().includes(contentType);
}
catch (IllegalArgumentException ex) {
throw new HttpMediaTypeNotSupportedException(
"Can't parse Content-Type [" + request.getContentType() + "]: " + ex.getMessage());
}
}
We are interested in piece request.getContentType(). There request is MockHttpServletRequest. Let's look on method getContentType():
public String getContentType() {
return this.contentType;
}
It just return value of this.contentType. It does not return a value from the header! And this.contentType is always NULL. Then contentType in matchMediaType methos will be always MediaType.APPLICATION_OCTET_STREAM.
Solution
I have tried many ways but have found only one that works.
Create package org.springframework.test.web.client in your test directory.
Create copy of org.springframework.test.web.client.MockMvcClientHttpRequestFactory but rename it. For example rename to FixedMockMvcClientHttpRequestFactory.
Find line:
MvcResult mvcResult = MockMvcClientHttpRequestFactory.this.mockMvc.perform(requestBuilder).andReturn();
Replace it with code:
MvcResult mvcResult = FixedMockMvcClientHttpRequestFactory.this.mockMvc.perform(new RequestBuilder() {
#Override
public MockHttpServletRequest buildRequest(ServletContext servletContext) {
MockHttpServletRequest request = requestBuilder.buildRequest(servletContext);
request.setContentType(request.getHeader("Content-Type"));
return request;
}
}).andReturn();
And register your ClientHttpReque
#Bean
public ClientHttpRequestFactory clientHttpRequestFactory(MockMvc mockMvc) {
return new FixedMockMvcClientHttpRequestFactory(mockMvc);
}
I know that it is not beautiful solution but it works fine.

Categories

Resources