Clean up redirect URL in Mockito tests - java

I have some Mockito tests which use MockMvc for controllers testing.
Controllers are using #ControllerAdvice which includes #ModelAttribute. It is needed because there are few attributes needed by 90% of controllers.
I clean up this information from redirect URLs by ignoreDefaultModelOnRedirect (it seems more efficient than adding RedirectAttributes parameters in all methods).
I need to test redirectUrl. But in this URL I see url + redirect attributes. I don't want to check with them because it is not how production behavior looks like.
Is there any way to pass ignoreDefaultModelOnRedirect to MockMvc?
== Edited
User go to greeting -> Redirected to greetingRedirected
#Controller
public class GreetingController {
#GetMapping("/greeting")
public String greeting(Model model) {
return "redirect:/greetingRedirected";
}
}
Model enriched with #ControllerAdvice data
#ControllerAdvice
public class GreetingControllerAdvice {
#ModelAttribute("attr")
public String getAttr() {
return "ATTRIBUTE";
}
}
And now start simple test:
#RunWith(MockitoJUnitRunner.class)
public class ApplicationTest {
private MockMvc mockMvc;
#Before
public void setUp() {
GreetingController greetingController = new GreetingController();
mockMvc = MockMvcBuilders.standaloneSetup(greetingController).setControllerAdvice(new GreetingControllerAdvice()).build();
}
#Test
public void greeting() throws Exception {
mockMvc.perform(get("/greeting"))
.andExpect(redirectedUrl("/greetingRedirected"));
}
}
As the consequence the result is:
java.lang.AssertionError: Redirected URL
Expected :/greetingRedirected
Actual :/greetingRedirected?attr=ATTRIBUTE
I can use SpringRunner of course and #Autowire MockMvc but MockitoRunner is faster (which is important for big project with thousands of tests).
I can get rid of setControllerAdvice(...) but it will make behavior much less similar to production behavior for most of the cases(because I have lots of tests which do not check on redirection behavior).
Finally I can split tests on "redirected tests" (with disabled advice) and "not redirected tests" but it will be quite silly. :)
So I'm asking if there is any simple way to tell MockMvc/Mockito that parameter spring.mvc.ignore-default-model-on-redirect is enabled and should behave like expected.

Related

Spring #ControllerAdvice not applied when packageName or assignableTypes is specified

A sample project demonstrating the issue can be found at: https://github.com/dirkbolte/javaexceptionhandlertest
I have a controller which is declared to produce JSON content:
#RestController
public class TestController {
#GetMapping(value = "exception", produces = MediaType.APPLICATION_JSON_VALUE)
public void test() {
}
}
The corresponding controller advice should map any exception to a BAD_REQUEST :
#RestControllerAdvice
public class MyExceptionHandler {
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(value = Exception.class)
public void exceptionhandler() {
}
}
I test that a call with a wrong accept header results in the correct error response code (as procudes specifies something different):
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
class TestControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void test() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders.get("/exception")
.accept(MediaType.APPLICATION_PDF)
).andExpect(status().isBadRequest());
}
}
The code above "fails successfully" :-) . In my production code I want to limit the controller advice to a certain controller (alternatively to a certain package), thus I specified assignableTypes on #ControllerAdvice: #RestControllerAdvice(assignableTypes = TestController.class) . Now the test fails as the exception handler isn't called anymore. Same happens when specifying basePackages or basePackageClasses - or when using an interface.
The version on GitHub shows the test failure, so I hope it is easy to reproduce.
How can I use a ControllerAdvice explicitly for one or more Controller?
The requested URL will not get mapped to any Controller method here. This would result in a Status 406 error (due to the specified accept type). Since no exception is thrown from the Controller methods the ControllerAdvice would not be invoked.
On dispatcher level, org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#getExceptionHandlerMethod will be called with handlerMethod = null which is why only generic controller advices are applied.
The ControllerAdvice would work here, if the request contains additional metadata that the controller method is configured for and/or an exception is thrown from within that method.

MockMvc can't validate with param #DateValid

I'm using MockMvc to test my method that has a date parameter. The parameter is annotated with #DateValid but MockMvc doesn't trigger the validator and returns success when I input a wrong date.
StudentControler
public ResponseEntity<?> getStudents(#PathVariable("id") String studentId,
#RequestParam("birthDate") #DateValid String Date) {
//do something
}
Test
public class StudentController extends AbstractControllerTest {
#Test
public void testStudents_validRequest() throws Exception {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("birthDate","aaaa");
MvcResult result = getMvc().perform(get("/student/{studentId}", "test")
.params(params)
.characterEncoding(StandardCharsets.UTF_8.name())
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json(mapToJson(studentResDto)))
.andReturn();
verify(studentService).getStudentId(anyString(), anyString());
log(log, result);
}
}
I do not understand why because when I test using Postman it works as expected. Postman does validate but MockMvc does not?
I using Spring-Boot 2.1.3
I can think of 2 problems here:
The validation annotation class is not loaded properly in your test. This seems to happen primarily when combining Spring validation with Hibernate validation (see also this question on how to fix that.
Your test is not setup properly. Could it be that you are not fully loading your controller in your mock test? Perhaps you are mocking the controller accidentally?

spring mvc how to test that my service persists entities in post request

So I'm writing this web app with Spring Boot using Spring Data with JPA and Spring MVC and I would like to make mock controller tests. I figured out how to test the get method, but in my controllers post method a new JPA entity is being either persisted or updated with my service. Here is what my controller looks like:
#Controller
#RequestMapping("/registerMember")
public class RegisterMemberController {
#Autowired
private MemberService memberService;
#GetMapping
public String index(RegisterMemberBean registerMemberBean) {
return "registerMember";
}
#PostMapping
public String handleSubmit(#Valid RegisterMemberBean registerMemberBean, BindingResult bindingResult, Model model) {
Member member = registerMemberBean.getMember();
boolean isRepeatPasswordCorrect = !isRepeatPasswordIncorrect(member.getPassword(), registerMemberBean.getComparePassword());
if(isAnyErrors(isRepeatPasswordCorrect, !bindingResult.hasErrors())) {
if(!isRepeatPasswordCorrect) {
model.addAttribute("isRepeatPasswordIncorrect", true).
addAttribute("isRepeatPasswordIncorrectMsg", "Passwords don't match");
}
return "registerMember";
}
boolean errUsername = !memberService.isNoOtherEntityWithUserName(0, member.getUserName());
boolean errEmail = !memberService.isNoOtherEntityWithEmail(0, member.getEmail());
if(errUsername || errEmail) {
if(errUsername) {
model.addAttribute("isExistingUserName", true).addAttribute("isExistingUserNameMsg", "Already a user with that username");
} if(errEmail) {
model.addAttribute("isExistingEmail", true).addAttribute("isExistingEmailMsg", "Already a user with that email");
}
return "registerMember";
}
getMainService().save(member);
return redirectTo("index", new RedirectEntity("member", member.getId()));
}
}
Now in my mock controller test i want to make make sure that my post method does the following:
Reload the page if the BindingResults has any errors
My service persists the member JPA entity in db (if no errors)
Method redirects me to the index page
This is what my (poor) test class looks like so far:
#RunWith(SpringRunner.class)
#TestPropertySource(locations="classpath:application_test.properties")
#WebAppConfiguration
public class RegisterMemberControllerTest {
private MockMvc mockMvc;
#MockBean
private MemberService memberService;
#MockBean
private RegisterMemberController controller;
#Before
public void init() {
mockMvc = MockMvcBuilders.standaloneSetup(controller).setViewResolvers(new StandaloneMvcTestViewResolver()).build();
controller.setMainService(memberService);
}
#Test
public void testIndex() throws Exception {
mockMvc.perform(get("/registerMember"))
.andExpect(status().isOk())
.andExpect(forwardedUrl("registerMember");
}
#Test
public void testHandleSubmit() throws Exception {
RegisterMemberBean registerMemberBean = new RegisterMemberBean();
registerMemberBean.setMember(TestFixture.getValidMemberWithoutReferences());
Member member = TestFixture.getValidMember();
mockMvc.perform(post(Page.REGISTER_MEMBER)).andExpect(status().isOk());
when(mockMvc.perform(post(Page.REGISTER_MEMBER)).andExpect((ResultMatcher) memberService.save(member)).andExpect(forwardedUrl("redirect:/index/member=" + member.getId() + ".html")));
}
}
to my understanding spring boot uses Mockito. I have some experience with EasyMock but I would like to use the spring defaults as much as possible. Can someone show how to achieve this?
I think there is a little bit of confusion on what should and shouldn't be mocked.
If I read your question correctly, you are actually trying to Unit Test your RegisterMemberController. Therefore, you most likely should NOT make a mock of that class, but actually test that class.
I believe that you would be creating fakes/dummies/stubs/mocks/spies of your MemberService, RegisterMemberBean, and BindingResult classes.
It would be these classes that would be created by your unit test and handed to your controller during the test that will force the testing of the logic that you are interested in proving/disproving.
FYI, when verifying that the MemberService class was called, that is where you would use a mock. The rest of the classes could either be dummies or stubs.
Side Note: I would recommend removing the Model parameter from your handleSubmit() method since it doesn't seem to be used anywhere.

Is there a way to verify that a method on Spring Controller was called using Mockito

This is my Mockito Test:
#RunWith(MockitoJUnitRunner.class)
public class Controller_Test {
private MockMvc mockMvc;
#InjectMocks
private EmployeeController employeeController;
#Mock
private InputValidationService inputValidationService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(restController).build();
}
#Test
public void testGetEmployeeDetails() {
EmployeeController spy = Mockito.spy(employeeController);
MvcResult result = mockMvc.perform(get("/employee/details/9816")).andDo(print()).andExpect(status().isOk()).andReturn();
// Have some basic asserts here on the result that are working fine
}
}
Now my question is, Is there a way to assert that the method I expected to be called in my controller was actually invoked.
I know it was, but how do I assert it with unit test
For e.g.
This is my RequestMapping in the controller:
#RequestMapping(value = "/employee/details/{id}", produces = "application/json; charset=UTF-8", method = RequestMethod.GET)
#ResponseStatus( HttpStatus.OK)
#ResponseBody
public EmployeeDetails getEmployeeDetailsById(#PathVariable String employeeID) {
//Some business logic
}
Now I would like to make some assertion like:
Mockito.verify(spy, times(1)).getEmployeeDetailsById();
So basically I would like to assert that the method I expected to get called was the one that got called. I know this can be done on the Mock Service object that I have i.e. inputValidationService but would like something similar for the controller as well.
Please let me know if there are any additional details that you would like me to post.
Maybe late, but I found org.springframework.test.web.servlet.result.HandlerResultMatchers which can verify the proper controller and method are being called. For example:
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.handler;
mockMvc
.perform(get("/employee/details/9816"))
.andExpect(handler().handlerType(EmployeeController.class))
.andExpect(handler().methodName("getEmployeeDetailsById"));
You can use #MockBean.This will use Mockito beans for which you will be able to use standart Mockito functions like "verify".
http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-testing-spring-boot-applications-mocking-beans

A spring integration test

In my integration test, I tried to use resttemplate to send a Get request to a dummy server created by MockMvcBuilders. However I got an error:
I/O error on GET request for "http://localhost:8080/test":Connection refused:
(In the function testAccess(), url is "http://localhost:8080/test"). My code is as below:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#IntegrationTest("server.port=8080")
public class MyTest {
private MockMvc mockMvc = null;
#Autowired
private WebApplicationContext context;
#Value("${server.port}")
private int port;
#Autowired
private MyController myController;
#Before
public void setUp(){
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.build();
}
#Test
public void testAccess() throws Exception{
RestTemplate restTemplate=new RestTemplate();
String url="http://localhost:8080/test";
try{
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
}
catch(ResourceAccessException ex){
System.out.println(ex.getMessage());
}
}
#Controller
public static class MyController {
#RequestMapping(value = "/test", method = RequestMethod.GET)
public #ResponseBody String access() {
return "OK";
}
}
}
The way I've done it is this:
First, you create a mock server from the actual RestTemplate you are using in your application
#Before
public void setup() throws Exception {
mockServer = MockRestServiceServer.createServer(myService.restTemplate);
}
Then you define how that request is going to work:
mockServer.expect(requestTo("http://localhost:8080/myrestapi"))
.andExpect(method(HttpMethod.POST))
.andRespond(withSuccess("{ success: true }", MediaType.APPLICATION_JSON));
And last you call the method in your application that will trigger a call to that url with that RestTemplate:
#Test
public void testThis() throws Exception {
myService.somethingThatCallsMyRestApi(parameters);
}
That will make your tests work as if there was a server up and running to process requests.
Using this in your example makes no sense, cause you would be testing that you build your test correctly and nothing else from the actual application.
The problem with this is that you cannot test dynamic responses. I mean, in my case the method I'm calling generates different data every time you call it and then sends it to the mockServer and then validates that the response matches in some very specific way. I haven't found a solution yet, but if the data you are going to send and receive is previously known, which would be in most cases, you'll have no problem using this.
Why are you defining a controller in your Test class and then trying to test it ? It doesn't feel logical to try to test something that is defined within the test it self.
Rather you would want to test a controller defined somewhere outside your tests, an actual controller that is used within your application.
Let's say MyController is defined as an actual controller then you could use the mockMvc object you created to test it.
mockMvc.perform(get('/test'))
.andExpect(status().isOk())

Categories