How to test if a controller method forwards the requests to a specific URL? - spring

In my Spring Boot application I have the following controller with a single method that redirects all HTML5 routes to the root URL**:
#Controller
public class RedirectController {
#RequestMapping(value = "/**/{path:[^\\.]*}")
public String redirect() {
return "forward:/";
}
}
How should I properly test that it works as expected?
Calling the content() method of the MockMvcResultMatchers class doesn't work:
#Test
public void givenPathWithoutDotShouldReturnString() throws Exception {
this.mockMvc.perform(get("/somePath"))
.andExpect(content().string("forward:/"));
}
>>> java.lang.AssertionError: Response content
>>> Expected :forward:/
>>> Actual :
** I found out about this solution from following this Spring tutorial.

When I called the andDo(print()) of the mockMvc class I got the following result:
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = /
Redirected URL = null
Cookies = []
Here I realized that Spring doesn't treat return "forward:/"; as a simple String result, but a URL forwarding (in a way it's pretty obvious), so the proper way to write the test is by calling the .andExpect() method with forwardedUrl("/") as an argument:
#Test
public void givenPathWithoutDotShouldReturnString() throws Exception {
this.mockMvc.perform(get("/somePath"))
.andExpect(forwardedUrl("/"));
}
The forwardedUrl() method comes from org.springframework.test.web.servlet.result.MockMvcResultMatchers.

Related

Junit testing- java.lang.AssertionError: Status Expected:<200> but was:<403>

I know this kind of questions has been asked before and I tried many of the answers given in there and none is doing good, So can someone please help me out in this for getting proper solution
Its my Controller class
#RestController
#RequestMapping("/admin")
public class ABController {
#PutMapping("/train/{trainId}/{status}")
public ResponseEntity updateTrainstatus(#PathVariable String trainId, #PathVariable String status) {
return feignClient.updateTrainstatus(trainId, status);
}
}
Its my test controller class
private final String UPDATE_TRAIN_URI = "/admin/train?trainId=train100&status=approved";
#Test
public void testJsonController() throws Exception {
String trainId = "train100";
String status = "approved";
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.put(UPDATE_TRAIN_URI)
.contentType(MediaType.APPLICATION_JSON_VALUE).accept(MediaType.APPLICATION_JSON).characterEncoding("UTF-8");
this.mockMvc.perform(builder).andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("Train status"))
.andDo(MockMvcResultHandlers.print());
}
Probably your project has a configured spring security. Than the controller requires some authentication - for instance a jwt token in a request header. In test your request does not have a valid value than You receive a 403 forbidden status

Validating if request body in HTTP POST request is null in Spring Boot controller

I am replacing manual validation of input to a POST request in a Spring Boot REST-controller. JSR-303 Spring Bean Validation is used for validating the instance variables in the request body and this is working as expected. What is the recommended method to validate that the object in the request body is not null?
I have tried:
annotating the entire object such as this: #NotNull #Valid #RequestBody Foo foo
annotating the entire class with #NotNull
I am replacing:
#PostMapping...
public ResponseEntity<Map<String, Object>> editFoo(
#RequestBody Foo foo, ...) {
if(foo == null) {
return (new ResponseEntity<>(headers, HttpStatus.BAD_REQUEST));
}
}
with a Bean Validation equivalent:
#PostMapping...
public ResponseEntity<Map<String, Object>> editFoo(
#Valid #RequestBody Foo foo, ...) {
...
}
I tried unit testing the controller method by:
// Arrange
Foo foo = null;
String requestBody = objectMapper.writeValueAsString(foo);
// Act + assert
mockMvc
.perform(
post("/end_point")
.contentType("application/json")
.content(requestBody))
.andExpect(status().isBadRequest());
I expected a MethodArgumentNotValidException which is handled by a #ControllerAdvice for this exception, but I get HttpMessageNotReadableException when executing the unit test.
My questions:
is it necessary to test if the request body is null?
if 1. is true, how should this be done with Bean Validation?
Seeing your code, you already check if the body is null. In fact #RequestBody has a default parameter required which defaults to true. So no need for Bean validation for that !
Your main issue here seems to be in your test. First of all it is good to write a test to validate your endpoint behavior on null.
However, in your test you does not pass null. You try to create a Json object from a null value with your objectMapper.
The object you are writting seems not to be a valid json. So when your sending this body, Spring says that it cannot read the message, aka the body of your request, as you say it is a application/json content but there is not json in it.
To test null body, just send your request in your test just removing the .content(requestBody) line and it should work !
--- Edit 1
I thought it was rejecting the message because of the body, but in fact it seems to work right away for me. Here is my controler and test so you can compare to your full code :
#RestController()
#RequestMapping("end_point")
public class TestController {
#PostMapping
public ResponseEntity<Map<String, Object>> editFoo(#RequestBody Foo foo) {
// if(foo == null) {
// return (new ResponseEntity<>(new HashMap<>(), HttpStatus.BAD_REQUEST));
// }
return (new ResponseEntity<>(new HashMap<>(), HttpStatus.OK));
}
}
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class TestControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private ObjectMapper objectMapper;
#Test
public void test_body_is_null() throws Exception {
Foo foo = null;
String requestBody = objectMapper.writeValueAsString(foo);
// Act + assert
mvc
.perform(
post("/end_point")
.contentType("application/json")
.content(requestBody))
.andExpect(status().isBadRequest());
}
}
This was made using Spring Boot 2.1.6.RELEASE
--- Edit 2
For the record if you want to use validation for null here, here is a snippet of the controller :
#RestController()
#RequestMapping("end_point")
#Validated
public class TestController {
#PostMapping
public ResponseEntity<Map<String, Object>> editFoo(#NotNull #RequestBody(required = false) Foo foo) {
return (new ResponseEntity<>(new HashMap<>(), HttpStatus.OK));
}
}
First you have to set required to false for the body, as default is true. Then you have to add the #NotNull annotation on the request body and #Validated on the controller.
Here if you launch your test you will see that the request fails with :
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is javax.validation.ConstraintViolationException: editFoo.foo: must not be null
As you said you had a #ControllerAdvice you can then map the exception as you wish !

How test Post request with custom object in content type application/x-www-form-urlencoded?

I have controller:
#PostMapping(value = "/value/", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String updateSettings(final Dto dto) {
System.out.println(">>> " + dto);
return "template";
}
Controller works if I send request across chrome window. But when I write test for this method I get problem. Not converted object, value not inserted.
Test:
#Test
#WithMockUser(username = FAKE_VALID_USER, password = FAKE_VALID_PASSWORD)
public void test_B_CreateDtoWithValidForm() throws Exception {
final Dto dto = new Dto();
dto.setId("value");
dto.setEnabled("true");
this.mockMvc.perform(post(URL_SET_PROVIDER_SETTINGS)
.contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.content(dto.toString()))
.andDo(print());
}
Output is >>> Dto{id=null, enabled=false}
How test Post request with custom object in content type application/x-www-form-urlencoded?
In this case you don't need to use content, but instead you need to use param in this way:
this.mockMvc.perform(post(URL_SET_PROVIDER_SETTINGS)
.contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.param("id", "value")
.param("enabled", "true"))
.andDo(print());

upgrading spring boot with groovy controller returns 406 causing HttpMediaTypeNotAcceptableException

I have a Groovy application that I am dealing with which is having some odd behavior when upgrading from spring-boot 1.3.0.RELEASE to 1.4.0.RELEASE. The controller always returns a 406 on any error and I am not sure what type of content it expects to return. The code is below:
SomeController.groovy:
#RestController
#RequestMapping('/some/mapping')
class SomeController extends AbstractController {
#Autowired
private SomeService someService
#RequestMapping(path = '/abc/{some_param}/some_action', method = RequestMethod.PUT, consumes = MediaType.TEXT_PLAIN_VALUE)
#ResponseStatus(HttpStatus.NO_CONTENT)
#PreAuthorize('isAuthenticated() && (principal.username == #username || principal.admin)')
void setValue(#PathVariable String some_param, #RequestBody String body_content) throws ValidationException, NotFoundException {
handleViolations(validate(AnObject, [some_param: some_param, body: body_content]))
try {
someService.setValue(some_param, body_content)
} catch(AlreadyExistsException e) {
throw new ValidationException([body: 'IN_USE'])
}
}
}
SomeControllerSpec.groovy < The test...
class AccountControllerSpec extends AbstractControllerSpec {
static final BASE_URL = 'http://localhost:8080/api/'
def client = new CustomRESTClient(BASE_URL)
// This test fails
def 'testing api'() {
//Expected 400 bad request but receiving a 406 not acceptable
client.put(
path: "/api/abc/fake_param/some_action",
// The body doesn't conform to the expectations of the API
body: 'blah',
contentType: MediaType.TEXT_PLAIN_VALUE
).status == HttpStatus.SC_BAD_REQUEST
// Exception thrown:
// INFO 22125 --- [tp1838490665-22] c.c.w.c.RestEndpointsConfiguration : org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
}
}
The Exception in the logs:
INFO 22125 --- [tp1838490665-22] c.c.w.c.RestEndpointsConfiguration : org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
I have tried many things including setting the expected header type:
client.setHeaders(accept: MediaType.TEXT_PLAIN_VALUE)
I have been trying various other things but to no avail. The exception persists.
Note: The action at the endpoint completes as expected.

Empty Exception Body in Spring MVC Test

I am having trouble while trying to make MockMvc to include the exception message in the response body. I have a controller as follows:
#RequestMapping("/user/new")
public AbstractResponse create(#Valid NewUserParameters params, BindingResult bindingResult) {
if (bindingResult.hasErrors()) throw BadRequestException.of(bindingResult);
// ...
}
where BadRequestException looks sth like this:
#ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "bad request")
public class BadRequestException extends IllegalArgumentException {
public BadRequestException(String cause) { super(cause); }
public static BadRequestException of(BindingResult bindingResult) { /* ... */ }
}
And I run the following test against /user/new controller:
#Test
public void testUserNew() throws Exception {
getMockMvc().perform(post("/user/new")
.param("username", username)
.param("password", password))
.andDo(print())
.andExpect(status().isOk());
}
which prints the following output:
Resolved Exception:
Type = controller.exception.BadRequestException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
MockHttpServletResponse:
Status = 400
Error message = bad request
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 = []
Does anybody have an idea on why is Body missing in the print() output?
Edit: I am not using any custom exception handlers and the code works as expected when I run the server. That is, running the application and making the same request to the server returns back
{"timestamp":1423076185822,
"status":400,
"error":"Bad Request",
"exception":"controller.exception.BadRequestException",
"message":"binding failed for field(s): password, username, username",
"path":"/user/new"}
as expected. Hence, there is a problem with the MockMvc I suppose. It somehow misses to capture the message field of the exception, whereas the default exception handler of the regular application server works as expected.
After opening a ticket for the issue, I was told that the error message in the body is taken care of by Spring Boot which configures error mappings at the Servlet container level and since Spring MVC Test runs with a mock Servlet request/response, there is no such error mapping. Further, they recommended me to create at least one #WebIntegrationTest and stick to Spring MVC Test for my controller logic.
Eventually, I decided to go with my own custom exception handler and stick to MockMvc for the rest as before.
#ControllerAdvice
public class CustomExceptionHandler {
#ExceptionHandler(Throwable.class)
public #ResponseBody
ExceptionResponse handle(HttpServletResponse response, Throwable throwable) {
HttpStatus status = Optional
.ofNullable(AnnotationUtils.getAnnotation(throwable.getClass(), ResponseStatus.class))
.map(ResponseStatus::value)
.orElse(HttpStatus.INTERNAL_SERVER_ERROR);
response.setStatus(status.value());
return new ExceptionResponse(throwable.getMessage());
}
}
#Data
public class ExceptionResponse extends AbstractResponse {
private final long timestamp = System.currentTimeMillis();
private final String message;
#JsonCreator
public ExceptionResponse(String message) {
checkNotNull(message, "message == NULL");
this.message = message;
}
}
This likely means that you either didn't handle the exception or you've really left the body empty. To handle the exception either add an error handler in the controller
#ExceptionHandler
public #ResponseBody String handle(BadRequestException e) {
return "I'm the body";
}
or user the global error handler if you're on 3.2 or above
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler
public #ResponseBody String handleBadRequestException(BadRequestException ex) {
return "I'm the body";
}
}
with this the body will be populate, you should populate it with your error message
Updated solution:
If you don't want to do a full integration test but still want to make sure the message is as expected, you can still do the following:
String errorMessage = getMockMvc()
.perform(post("/user/new"))
...
.andReturn().getResolvedException().getMessage();
assertThat(errorMessage, is("This is the error message!");

Resources