Spring MVC test post method with controller redirect - spring

I have a test:
#Test
public void shouldAddCompany() throws Exception {
mockMvc.perform(post("/companies")
.param("name", "companyName"))
.andExpect(model().attribute("company",
hasProperty("name", is("companyName"))));
}
and my controller method looks like that:
#PostMapping("/companies")
public String displayCompaniesPost(#ModelAttribute Company company) {
companyService.save(company);
return "redirect:/companies";
}
How can i check company attribute in test? There is a problem because of redirect and status 302.
java.lang.AssertionError: Model attribute 'company'
Expected: hasProperty("name", is "companyName")
but: was null
I think it occurs because controller is going to GET method because of redirection. When I remove this redirection everything is ok, but I don't want to remove that redirection.
EDIT (GetMapping):
#GetMapping({"/", "/companies"})
public String displayCompanies(Model model) {
model.addAttribute("company", new Company());
List<Company> companies = companyService.findAll();
model.addAttribute("companies", companies);
return "companies";
}
I thought the problem is because of addding attribute with the same name in getMapping, but when I removed it, it still doesn't work.

You need to modify your approach. If you POST to a controller method, and it returns a Redirect you will have no ability to access any model information set by that controller, it just returns an HTTP 302 with a Location Header to the client telling it the new url to go to (in this case GET /companies). If this is a strictly Unit test, that is the extent of what you can test for this method.
I would consider instead treating this as an integration test, and change your test to have two separate steps:
POST /companies and validate that the response is the expected redirect
GET /companies and validate that the list of companies returned contains the new company you posted in step 1

Related

How do I return a template(thymeleaf) in spring boot and resolve it to a particular endpoint

Short: I want to use Thymeleaf template index.html but have the url point to thanks.html.
In depth: I am trying to have a form submission take my user to a page http://localhost:8080/thanks.html. I dont want the action of the form to be thanks.html for a few different reasons but I have greatly simplified the logic below. When all of the validation of the form are passed, I want to pass in a variable to indicate which layout to use. I have that working by using a model variable called contentPage. The problem is that if i have "return "thanks.html";" in the indexSubmit Method I get an error from thymeleaf saying template not found. If I change that to "return "index.html"; everything works but the url is http://localhost:8080/ instead of http://localhost:8080/thanks.html.
#PostMapping("/")
public String indexSubmit(Model model) {
model.asMap().clear();
model.addAttribute("contentPage","layout/thanks.html");
return "thanks.html";
}
#GetMapping("/thanks.html")
public String thanks(Model model) {
model.addAttribute("contentPage","layout/thanks.html");
return "index.html";
}
I fond an answer on my own:
return "redirect:thanks.html";
Thanks,
Brian

Why I can't redirect from a Spring MVC controller method to another controller method?

I am pretty new in Spring MVC and I have some problem trying to redirect to a controller method after that another controller method terminate its execution.
So I have the following situation. Into a controller class I have this method that correctly handle POST request toward the validaProgetti resource:
#RequestMapping(value = "validaProgetti", method=RequestMethod.POST)
public #ResponseBody String validaProgetti(#RequestBody List<Integer> checkedRowList) {
System.out.println("ID progetti da aggiornare: " + checkedRowList);
List<Twp1007Progetto> progettiDaValidare = new ArrayList<Twp1007Progetto>();
for (int i=0; i<checkedRowList.size(); i++) {
System.out.println("ID PROGETTO: " + checkedRowList.get(i));
progettiDaValidare.add(progettoService.getProgetto(checkedRowList.get(i)));
}
progettoService.validaProgetti(progettiDaValidare);
return "redirect:ricercaValidazione";
}
So this method is correctly mapped and when the validaProgetti resource is called it is executed.
At the end of this method I don't return a view name that render a JSP page but I have to redirect to another method (that do something and render a JSP page). So, instead to return a view name, I redirect toward another resource:
return "redirect:ricercaValidazione";
Then in the same controller class I have declared this method that handle request toward this ricercaValidazione resource:
#RequestMapping(value = "ricercaValidazione", method=RequestMethod.POST)
public String ricercaValidazione(#ModelAttribute ConsultazioneFilter consultazioneFilter, Model model, HttpServletRequest request) {
RicercaConsultazioneViewObject filtro = null;
try {
filtro = new ObjectMapper().readValue(request.getParameter("filtro"), RicercaConsultazioneViewObject.class);
filtro.setSelStatoProgetto(3); // Progetti da validare
} catch (IOException e) {
logger.error(e);
}
consultazioneFilter = new ConsultazioneFilter(filtro);
model.addAttribute("consultazioneFilter", consultazioneFilter);
model.addAttribute("listaProgetti", new ListViewObject<Twp1007Progetto>(progettoService.getListaProgettiConsultazione(consultazioneFilter)) );
return "validazione/tabellaRisultati";
}
The problem is that it can't work and after the redirection can't enter into the ricercaValidazione() method.
I think that maybe the problem is that this ricercaValidazione() method handle POST request toward the ricercaValidazione resource and the return "redirect:ricercaValidazione"; maybe generate a GET request.
But I am not sure about it.
Why? What am I missing? How can I solve this issue?
Tnx
the redirect and fordward prefix are for resolving views; you are tring to redirect from one controller to another one. This can be done but redirect works in the following way
A response is sent to the browser with the redirect http status code and and url
The browser loads via GET the request URL
Your Spring controller (and the corresponding ammping method) is invocated if it matches the annotation params
From what you write I'm not sure this is what you really want; as you already noted there is a mismatch between HTTP methods (GET vs POST).
Your second method ricercaValidazione expects a filtro param in order to filter some data, but in the validaProgetti there is nothing similar, so it seems that the two controllers are not directly chainable. If what you want is to display a page after validaProgetti that shows a form and the the user can submit it you must add a method annotated with a method GET and url ricercaValidazione; the new method must return the view containing the form; which points via POST to url of validaProgetti. In this way you can redirect from ricercaValidazione to validaProgetti
Give mapping name of your controller with redirect like
redirect:/controll-mapping_name/ricercaValidazione
have a look on this question
Unable to redirect from one controller to another controller-Spring MVC

Web API 2 attribute routing returning 404

I'm having trouble getting the Web API 2 attribute routing to work.
I've been trying everything I could find this whole evening but I can't find the problem.
What I want to achieve is the following:
Make a POST request to http://localhost:xxxx/api/chat/joingroup/1234 to get to the following API call:
[Route("joingroup/{id}")]
[HttpPost]
public async Task<IHttpActionResult> JoinGroup(string id, string connectionID)
{
await hubContext.Groups.Add(connectionID, id);
return Ok(hubContext.Groups.ToString());
}
This keeps getting me a http 400 message.
{"message":"No HTTP resource was found that matches the request URI 'http://localhost:41021/api/chat/joingroup/123'.",
"messageDetail":"No action was found on the controller 'Chat' that matches the request."}
But sending a post to: http://localhost:41021/api/chat/sendmessage/pm/123123 and also to http://localhost:41021/api/chat/joingroup gives me a 200
The chatcontroller:
[RoutePrefix("api/chat")]
public class ChatController : ApiController
{
IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
[...]
[Route("joingroup/{id}")]
[HttpPost]
public async Task<IHttpActionResult> JoinGroup(string id, string connectionID)
{
await hubContext.Groups.Add(connectionID, id);
return Ok(hubContext.Groups.ToString());
}
HTTP POSTS to http://localhost:xxxx/api/chat/sendmessage are working fine.
I cannot figure out why it isn't going to the correct method when I'm calling a POST on http://localhost:xxxx/api/chat/joingroup/1234.
SOLUTION:
The solution was to reference both values that are needed in the JoinGroup method, id and connectionID. Now the request will hit this method.
Using:
http://localhost:xxxx/api/chat/joingroup/john?connectionid=123 will work.
I noticed two things on the code you sent through:
the path you POST to is: localhost:xxxx/joingroup/1234 , this
should be localhost:xxxx/api/chat/joingroup/1234
because you have 2 parameters for the joingroup, you will need to pass both of them through, may be like this localhost:xxxx/api/chat/joingroup/1234?connectionID=value or you can pass it on the request body
if the connectionID is optional you can modify the method to use option al parameters like this
public string JoinGroup(string id, string connectionID = "")
please let me know if this helps.
Thanks
Ashraf
I assume the connectionID parameter references the POSTed data. The easiest thing to make it work is to decorate it with the [FromBody] attribute and put an = in front of the value being sent like this: =MyConnection1.
Web API expects an object with properties or an array otherwise. Alternatively, you can wrap the connection ID with a custom class and pass it serialized as JSON/XML.

Spring controller, why is the returned view ignored?

So, say I have an existing, working page Display Cashier, which displays information about a cashier in a shop. Now, I add a button to this page that looks like:
Manager
The request-mapping for this URL maps it (successfully) to a controller: HandleGetManager
the HandleGetManager controller looks like this:
#Controller
public class HandleGetManager{
private employeeBO employeeBO; //BO handles all business logic
//spring hooks
public HandleGetManager(){}
public void setemployeeBo(employeeBO employeeBO){
this.employeeBO = employeeBO;
}
//get controller
#RequestMapping(method=RequestMethod.GET)
public String getManager(#RequestParam String cashierId){
Long managerId = employeeBO.getManagerByCashierId(cashierId);
String redirectUrl = "/displayManager.ctl?managerId=" + managerId.toString();
return redirectUrl;
}
}
Here's what happens when I try it:
I hit the new button on the Display Cashier page, I expect the following to happen:
The browser sends a get request to the indicated URL
The spring request-mapping ensures that the flow of control is passed to this class.
the #RequestMapping(method=RequestMethod.GET) piece ensures that this method is evoked
The #RequestParam String cashierId instructs Spring to parse the URL and pass the cashierId value into this method as a parameter.
The EmployeeBo has been injected into the controller via spring.
The Business logic takes place, envoking the BO and the managerId var is populated with the correct value.
The method returns the name of a different view, with a new managerId URL arg appended
Now, up until this point, everything goes to plan. What I expect to happen next is:
the browsers is directed to that URL
whereupon it will send a get request to that url,
the whole process will start again in another controller, with a different URL and a different URL arg.
instead what happens is:
this controller returns the name of a different view
The browser is redirected to a half-right, half wrong URL: handleGetManager.ctl?managerId=12345
The URL argument changes, but the name of the controller does not, despite my explicitly returning it
I get an error
What am I doing wrong? Have I missed something?
Assuming you have a UrlBasedViewResolver in your MVC configuration, the String value you return is a View name. The ViewResolver will take that name and try to resolve a View for it.
What you seem to want to do is to have a 301 response with a redirect. With view names, you do that by specifying a redirect: prefix in your view name. It's described in the documentation, here.
Here's a question/answer explaining all the (default) ways you can perform a redirect:
How can I prevent Spring MVC from doing a redirect?

Spring redirect: prefix issue

I have an application which uses Spring 3. I have a view resolver which builds my views based on a String. So in my controllers I have methods like this one.
#RequestMapping(...)
public String method(){
//Some proccessing
return "tiles:tileName"
}
I need to return a RedirectView to solve the duplicate submission due to updating the page in the browser, so I have thought to use Spring redirect: prefix. The problem is that it only redirects when I user a URL alter the prefix (not with a name a resolver can understand). I wanted to do something like this:
#RequestMapping(...)
public String method(){
//Some proccessing
return "redirect:tiles:tileName"
}
Is there any way to use RedirectView with the String (the resolvable view name) I get from the every controller method?
Thanks
the call prefixed by redirect: is a url, which is sent in a standard browser 302 redirect. you can't redirect to a view, because a view isn't a url. instead you'll need a new servelet mapping to a 'success' view and then redirect to that instead
#RequestMapping("processing.htm")
public String method(){
//Some proccessing
return "redirect:success.htm"
}
#RequestMapping("success.htm")
public String method(){
return "tiles:tileName"
}
this case works fine when you just need to show a 'thank you' page, which requires no specific data from the processing stage. however, if your success page needs to show some information from the processing, there are 2 ways to do it.
1) pass the information in the url as a get post ("redirect:success.htm?message=hi"). this is incredibly hackable, and thus highly unrecommended.
2) the better way is to store information in the http session, using #SessionAttributes and #ModelAttribute

Resources