I am thoroughly learning Spring Boot, I have come towards the end and this seems to be the last sticking point.
I feel what I am not getting is one of those things that is so simple and straight forward, It's not even explained.
Anyway, I have a simple CRUD demo, where I have a form that is posted to create an entity that is hard coded, once I submit the form, there is a redirect to the page that shows the new entity created.
It works perfectly in the web browser tested many times, and in postman, but for some reason when I am doing @WebMvcTest and @SpringBootTest, I get the correct 302 response status, but THERE IS NO RESPONSE BODY from the redirect response.
I did read this question: https://stackoverflow.com/a/45555607/21505152
Even the main docs lack any info: https://docs.spring.io/spring-framework/reference/testing/spring-mvc-test-framework/vs-end-to-end-integration-tests.html
Which basically shows that MockMvc might not be able to test such scenarios, if so, what is the alternative ? Do I even need to test this ?
What is going on ? As everything is working perfectly in the browser
Here is the code:
@Controller
@RequestMapping("/dummy-entities")
public class DummyEntityController {
private DummyEntityDao dao;
public DummyEntityController(DummyEntityDao dao) {
this.dao = dao;
}
@GetMapping("")
public String getAll(Model model) {
model.addAttribute("mainHeading", "Dummy Entities");
model.addAttribute("dummyEntities", dao.getDummyEntities());
return "dummyentities/dummy-entities";
}
@GetMapping("/{id}")
public String getById(@PathVariable int id, Model model) {
try {
model.addAttribute("dummyEntity", dao.getDummyEntityById(id));
return "dummyentities/dummy-entity";
}catch (DummyEntityNotFoundException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage());
}
}
@GetMapping("/add")
public String getAddDummyEntityView() {
return "dummyentities/add";
}
@PostMapping("/add")
public String addDummyEntity(@ModelAttribute DummyEntity dummyEntity) {
DummyEntity newDummyEntity = dao.createDummyEntity(dummyEntity);
return "redirect:/dummy-entities/" + newDummyEntity.getId();
}
@GetMapping("/edit/{id}")
public String getEditDummyEntityView(@PathVariable int id, Model model) {
try {
DummyEntity dummyEntity = dao.getDummyEntityById(id);
model.addAttribute("dummyEntity", dummyEntity);
return "dummyentities/edit";
}catch (DummyEntityNotFoundException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage());
}
}
@PostMapping("/edit/{id}")
public String editDummyEntity(@ModelAttribute DummyEntity dummyEntity) {
try {
dao.updateDummyEntityById(dummyEntity);
}catch (DummyEntityWriteException e) {
}
return "redirect:/dummy-entities/" + dummyEntity.getId();
}
@RequestMapping("/delete/{id}")
public String deleteDummyEntity(Model model, @PathVariable int id) {
try {
dao.deleteDummyEntityById(id);
model.addAttribute("deletedDummyEntityId", id);
return "dummyentities/delete";
}catch (DummyEntityNotFoundException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage());
}catch (DummyEntityWriteException e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
}
}
}
class DummyEntityControllerIntegratedTest extends MvcThymeleafApplicationTests {
@Test
void createAddsOne() throws Exception {
DummyEntity dummyEntity = new DummyEntity(0, "DE-13", "dek13", "dev13", 10, 20, 30);
mockMvc.perform(
MockMvcRequestBuilders.post("/dummy-entities/add")
.param("name", "DE-13")
.param("key", "dek13")
.param("value", "dev13")
.param("x", Integer.valueOf(10).toString())
.param("y", Integer.valueOf(20).toString())
.param("z", Integer.valueOf(30).toString())
).andExpect(result -> assertEquals(302, result.getResponse().getStatus()));
}
@Test
void createRedirectsToDehome () throws Exception {
DummyEntity dummyEntity = new DummyEntity(0, "DE-13", "dek13", "dev13", 10, 20, 30);
Map<String, Object> model = mockMvc.perform(
MockMvcRequestBuilders.post("/dummy-entities/add")
.param("name", "DE-13")
.param("key", "dek13")
.param("value", "dev13")
.param("x", Integer.valueOf(10).toString())
.param("y", Integer.valueOf(20).toString())
.param("z", Integer.valueOf(30).toString())
).andReturn().getModelAndView().getModel();
DummyEntity newDummyEntity = (DummyEntity) model.get("dummyEntity");
assertNotNull(newDummyEntity);
}
}
It works perfectly in the browser but the second test is failing. The way I see it, it is being redirected, which means the client should be making a request again to the end point that gets the new id and it should be returning this ?
I did not try with RedirectAttributes, as I am not sure its necessary, if this simulates a new request then when it hits the controller end point, everything should flow as normal ?
I tried to debug, it but I found out that the redirected end point never gets called during test, but I assume its working because the end result in the browser is perfect, it just does not work in these tests, Why is this ?
I would appreciate any help
@Test
void createRedirectsToDehome () throws Exception {
//POST request triggers the redirect
mockMvc.perform(
MockMvcRequestBuilders.post("/dummy-entities/add")
.param("name", "DE-13")
.param("key", "dek13")
.param("value", "dev13")
.param("x", Integer.valueOf(10).toString())
.param("y", Integer.valueOf(20).toString())
.param("z", Integer.valueOf(30).toString())
)
// Assert/check response - 302 redirect
.andExpect(status().is3xxRedirection())
// Capture the target URL
.andExpect(redirectedUrlPattern("/dummy-entities/*"))
//follow redirect manually by using get req
.andDo(result -> {
String redirectUrl = result.getResponse().getRedirectedUrl();
// Perform GET on redirected URL
mockMvc.perform(MockMvcRequestBuilders.get(redirectUrl))
// Expected 200 OK from redirected URL
.andExpect(status().isOk())
// expected view name
.andExpect(view().name("dummyentities/dummy-entity"));
});
}
is3xxRedirection(): This matcher checks if the response status code is within the 3xx range (300-399), which includes: 302 (Found), 303 (See Other), 301 (Moved Permanently), 307 (Temporary Redirect), 308 (Permanent Redirect), etc.
use andDo() to perform additional actions //following the redirect manually U can follow by extracting the redirected Url and perform Get request then ensure the status code 200 and view rendered matches providing .view().name()