I am trying to unit test SpringBoot Controller. Objective is to test that we return 406 Not Acceptable when request header contains Accept as anything other than application/json. But I find its not working with MockMvc. I am getting 200 OK instead of 406. Any ideas please! Of course, service returns 406 as expected when I test using Postman or any rest client.
#Test
public void shouldRejectIfInvalidAcceptHeaderIsPassed() throws Exception {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Accept","application/xml");
httpHeaders.add("Authorization", "some jwt token");
this.mockMvc.perform(post("/sample/test")
.contentType(MediaType.APPLICATION_JSON)
.headers(httpHeaders)
.content(toJson("")))
.andExpect(status().isNotAcceptable());
// Then
verify(mockSampleService).getSampleOutput();
}
And my controller looks like,
#RestController
#Validated
#RequestMapping(PATH_PREFIX)
public class SampleController {
public static final String PATH_PREFIX = “/sample”;
private final SampleService sampleService;
#Autowired
public SampleController(SampleService sampleService) {
sampleService = sampleService;
}
#RequestMapping(value = “/test”, method = RequestMethod.POST))
public SampleResponse createSession() {
return sampleService.getSampleOutput();
}
}
You might consider using the accept() method instead, e.g.
mvc.perform( MockMvcRequestBuilders
.get("/some/test/url")
.content(...)
.accept(MediaType.APPLICATION_XML)
.andExpect(...)
Ahh! I have identified and fixed the issue. In the controller we need to have, #RequestMapping(headers="Accept=application/json"). Else MockMvc is not able to identify the expected format.
Related
I do not know why the MvcResult returns an empty JSON for a GET request when the httpStatus is Ok. Moreover, through Postman I can tell that the data is indeed there and should be retrievable. I want to use the JSON data and map it to objects using the ObjectMapper's readValue() method. But in order to do that, I obviously need to be able to retrieve it first... help
The test does not fail, but when I try to System.out.println(json) - there is an empty line in the console where presumably the printed JSON should be. (I do not have the same issue when doing unit tests.)
`#SpringBootTest
#AutoConfigureMockMvc (addFilters = false) //in order to disable security: error 401
public class IntegrationTests {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Test
public void test() {
String url = "/url";
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(url))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
String json = result.getResponse().getContentAsString();
System.out.println(json);
}`
I'm currently struggeling returning http response status-codes on certain conditions. Let's say, the return objetct of taskService.getNewTasks is null. In this case I want to return status-code 404. On some exception I want to return some 50x, and so on.
My code so far
#RestController
public class TaskController {
#Autowired
private TaskService taskService;
#GetMapping(path = "gettasks")
private Future<Tasks> getNewTasks() {
return taskService.getNewTasks();
}
...
}
#Service
public class TaskService {
#Async
public Future<Tasks> getNewTasks() {
...
return CompletableFuture.completedFuture(tasks);
}
}
This could suit you.
#GetMapping(path = "gettasks")
private CompletableFuture<ResponseEntity<Tasks>> getNewTasks() {
CompletableFuture<Tasks> future = new CompletableFuture<>();
future.complete(taskService.getNewTasks());
if(yourCondition){
return future.thenApply(result -> new ResponseEntity<Tasks>(result, HttpStatus.STATUS_1));
}
return future.thenApply(result -> new ResponseEntity<Tasks>(result, HttpStatus.STATUS_2));
}
As described in https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-return-types, Future isn’t supported as return type for controller handler methods.
Since you’re using CompletableFuture you can just return that or CompletionStage, which are supported by spring.
If that completes with an exception, you can use the regular Spring exception handling mechanisms like annotating the exception with #ResponseStatus .
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.
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/
The context
I am currently working on an educational project. This implies two Spring Boot REST servers. One is an actual server, which does some processing.
The one I'm interested in is the other. It is a proxy which will redirect all calls to the first one. So that when I call http://localhost:8080/foo, my proxy server will in turn call http://localhost:8090/foo. And if the first server returns A, the proxy will return {"proxied": A, "someInformationAboutThisCall": B}.
I managed to get to this point with some probably inelegant but functioning code of which I give an excerpt below. The key here is that I use #RequestMapping("**") to achieve this. The next step is to design an interface that will make my additional information immediately legible, which is basically the point of this project. If I remove all #RequestMapping("**"), it works just fine.
The question
Now my problem is the following: having used #RequestMapping("**"), I cannot serve static content (the calls get redirect to the other REST server, which does not serve static content). How could I configure Spring Boot/Spring MVC to ignore resources available as static content when mapping the requests, or make the PathResourceResolver prioritary over my controller?` Or should I serve my static content from yet another JVM/server?
Thanks in advance for your help!
Edit of interest: while doing some tests, I discovered that the static content is served, with some restrictions, if I use #RequestMapping("*").
/index.html generates an error page (as does more generally any static content directly in public)
/itf/index.html works (as does more generally any file in public/itf or any other subdirectory of public)
/itf does not work: Spring Boot seems unaware of an index file in it. I must specify a full URI, down to the specific file I want to display.
This however does not work at all with #RequestMapping("**"), which I need.
The tentatives
I tried using a WebMvcConfigurerAdapter with an HandlerInterceptorAdapter (found on SO, SO again and many other places on the Internet), but could not start my project anymore because Spring boot then does not find the InterceptorRegistry bean (has there been recent changes in Spring Boot? I'm using the version 1.5.3.RELEASE).
I also tried some anti-matching but not only does it not work, it also feels very very dirty (and this whole project is probably not optimal, so that's saying a lot).
The code samples for the curious
My "proxy" controller
Note: you can suggest better ways to realize this in comments. Please keep in mind that, though I'm always open to enhancement suggestions, this was not my question.
#RestController
public class ProxyController {
#Value("${monitored.url.base}") // "http://localhost:8090"
private String redirectBase;
#RequestMapping(value = "**", method = {RequestMethod.POST, RequestMethod.PUT})
public ProxiedResponse proxifyRequestsWithBody(HttpServletRequest request, #RequestHeader HttpHeaders headers, #RequestBody Object body) throws URISyntaxException {
return proxifyRequest(request, headers, body);
}
#RequestMapping(value = "**")
public ProxiedResponse proxifyRequestsWithoutBody(HttpServletRequest request, #RequestHeader HttpHeaders headers) throws URISyntaxException {
return proxifyRequest(request, headers, null);
}
private ProxiedResponse proxifyRequest(HttpServletRequest request, #RequestHeader HttpHeaders headers, #RequestBody Object body) throws URISyntaxException {
final RequestEntity<Object> requestEntity = convertToRequestEntity(request, headers, body);
// call remote service
final ResponseEntity<Object> proxied = restTemplate.exchange(requestEntity, Object.class);
// Return service result + monitoring information
final ProxiedResponse response = new ProxiedResponse();
response.setProxied(proxied.getBody());
// set additional information
return response;
}
// Won't work properly for POST yet
private <T> RequestEntity<T> convertToRequestEntity(HttpServletRequest request, HttpHeaders headers, T body) throws URISyntaxException {
// Build proxied URL
final StringBuilder redirectUrl = new StringBuilder(redirectBase).append(request.getRequestURI());
final String queryString = request.getQueryString();
if (queryString != null) {
redirectUrl.append("?").append(queryString);
}
// TODO enhancement: transmit headers and request body to make this a real proxy
final HttpMethod httpMethod = HttpMethod.valueOf(request.getMethod());
return new RequestEntity<>(body, headers, httpMethod, new URI(redirectUrl.toString()));
}
}
My dirty attempt at excluding static resources URLs
#Configuration // adding #EnableWebMvc did not solve the problem
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
private static class StaticResourcesHandlerInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
final String requestURI = request.getRequestURI();
if (requestURI == null || "/".equals(requestURI) || "/index.html".equals(requestURI) || requestURI.startsWith("/assets")) {
return super.preHandle(request, response, null);
}
return super.preHandle(request, response, handler);
}
}
#Autowired
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new StaticResourcesHandlerInterceptor()).addPathPatterns("/**");
}
}
You can split the path into a wild-card, and a named path variable which must match a negative lookahead regular expression.
#RequestMapping("/{variable:(?!static).*}/**")
You can then use #PathVariable String variable as an argument of your controller method to obtain the value of variable if you need to pass it.
(Would rather have written a comment but I have insufficient reputation)
Try to add the #EnableWebMvc annotation to your configuration:
#Configuration
#EnableWebMvc
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
...
}
I have a simple controller test that looks like this
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = CuponzaApiApplication.class)
#WebAppConfiguration
public class UserControllerTest {
private MockMvc mockMvc;
#Autowired
protected WebApplicationContext wac;
#Autowired
UserRepository userRepository;
#Before
public void setUp(){
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
#Test
public void createUser() throws Exception{
CuponzaUser user = new CuponzaUser("some#test.com", "firstName", "lastName");
ObjectWriter jackson = new ObjectMapper().writer().withDefaultPrettyPrinter();
mockMvc.perform(post("/user/add").content(jackson.writeValueAsString(user)).contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"));
}
however it fails saying the following
java.lang.AssertionError: Content type not set
here is my controller
#RestController
public class UserController {
#Autowired
UserRepository userRepository;
#RequestMapping(value = "/user/add",method = RequestMethod.POST,produces={MediaType.APPLICATION_JSON_VALUE})
public void AddUser(#RequestBody CuponzaUser user, HttpServletResponse response){
if(user ==null){
response.setStatus(HttpStatus.BAD_REQUEST.value());
return;
}else{
user.setCreationDate(new Date());
user.setLastSeenDate(new Date());
userRepository.save(user);
//response.addHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE);
return;
}
}
I dont want to manually add the content type header for each response , and i thought that the "produces" annotation should take care of this
any ideas?
This tends to be a little confusing - produces parameter of a #RequestMapping annotation does not really modify the response header, it is a way to narrow down the appropriate handler method based on the Accept header that the user has specified. Think of it this way, #RequestMapping and all the parameters associated with it is just a way to filter down to the appropriate method for Spring MVC to call.
The MessageConverter responsible for converting the responses to the appropriate media type does plug in the response Content-Type header, I think the issue in your case is because you are not setting the Accept header in your mock test - .accept(MediaType.APPLICATION_JSON)
The issue is that you're not returning anything. Your response body is empty.
In a way it makes sense, there is no content, what would be the point in defining a Content-Type? Setting the Accept header also won't get you anywhere. Furthermore, you should be able to reproduce this same behaviour outside of your unit tests too, i.e. it's not an issue with your unit test/mock setup.
You could either:
return some content
consider returning a 204 (No Content), if you really don't want to return anything (still wouldn't give you a Content-Type header but it would make clear that there is no content)
add the header manually as in the workaround commented out in your question