This is my test:
#Test
public void shouldProcessRegistration() throws Exception {
Spitter unsaved = new Spitter("Gustavo", "Diaz", "gdiaz", "gd123");
Spitter saved = new Spitter(24L, "Gustavo", "Diaz", "gdiaz", "gd123");
SpitterRepository spittlerRepository = Mockito.mock(SpitterRepository.class);
Mockito.when(spittlerRepository.save(unsaved)).thenReturn(saved);
SpitterController spittleController = new SpitterController(spittlerRepository);
MockMvc mockSpittleController = MockMvcBuilders.standaloneSetup(spittleController).build();
mockSpittleController.perform(MockMvcRequestBuilders.post("/spitter/register")
.param("firstName", "Gustavo")
.param("lastName", "Diaz")
.param("userName", "gdiaz")
.param("password", "gd123"))
.andExpect(MockMvcResultMatchers.redirectedUrl("/spitter/" + saved.getUserName()));
Mockito.verify(spittlerRepository, Mockito.atLeastOnce()).save(unsaved);
}
This is my controller:
#Controller
#RequestMapping(value = "spitter")
public class SpitterController {
SpitterRepository spitterRepository;
#Autowired
public SpitterController(SpitterRepository spittlerRepository) {
this.spitterRepository = spittlerRepository;
}
#RequestMapping(value = "/register", method = RequestMethod.POST)
public String processRegistration(Spitter spitter){
spitterRepository.save(spitter);
return "redirect:/spitter/" + spitter.getUserName();
}
}
I want to verify that spitterRepository.save was called passing the same unsaved object I defined in the test. But i'm getting this exception:
Argument(s) are different! Wanted:
spitterRepository.save(
spittr.Spitter#3bd82cf5
);
-> at spitter.controllers.test.SpitterControllerTest.shouldProcessRegistration(SpitterControllerTest.java:48)
Actual invocation has different arguments:
spitterRepository.save(
spittr.Spitter#544fa968
);
Use an ArgumentCaptor to capture the value passed to save, and then assert on it.
ArgumentCaptor<Spitter> spitterArgument = ArgumentCaptor.forClass(Spitter.class);
verify(spittlerRepository, atLeastOnce()).save(spitterArgument.capture());
assertEquals("Gustavo", spitterArgument.getValue().getName());
For asserting if the Bean is the same, I would recommend you to use Hamcrest's samePropertyValues (http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/beans/SamePropertyValuesAs.html)
If I understand correctly, the below line initializes a mock where you can utilize the capabilities in Spring.
MockMvc mockSpittleController = MockMvcBuilders.standaloneSetup(spittleController).build();
When you call the mock like below, you will not call the method with the unsaved object. Rather, I guess a new object will be created.
mockSpittleController.perform(MockMvcRequestBuilders.post("/spitter/register")
.param("firstName", "Gustavo")
.param("lastName", "Diaz")
.param("userName", "gdiaz")
.param("password", "gd123"))
.andExpect(MockMvcResultMatchers.redirectedUrl("/spitter/" + saved.getUserName()));
This makes the verification fail, because the instances will not be the same.
To solve this, you should make sure that Spitter implements equals() and then use the eq() matcher for the verification:
Mockito.verify(spittlerRepository, Mockito.atLeastOnce()).save(org.mockito.Matchers.eq(unsaved));
This will check if the expected argument equals() what was passed.
import org.mockito.Matchers;
//...
Mockito.verify(spittlerRepository, Mockito.atLeastOnce()).save(Matchers.refEq(unsaved));
Related
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/
I'd like to display a warning message on specific pages 5 minutes prior to a system shutdown. Rather than add it manually to each these pages I created a #ControllerAdvice class with a #ModelAttribute method that adds the message to the Model parameter, but from what I understand reading the documentation and SO and some initial testing this model attribute will be added to every method with a #RequestMapping.
I realize I could refactor my code so that the targeted methods are all in one controller and limit the #ControllerAdvice to that one controller, but I would end up with a collection of otherwise non-related methods in that controller which muddies up the overall structure of my controllers.
So, is there a way to indicate which specific methods in multiple controllers the #ModelAttribute is applied to? Would a custom annotation be a solution (not sure how that would work)? I'd like to do this via annotations if possible.
Edit:
The #ControllerAdvice code is pretty basic:
#ControllerAdvice
public class GlobalModelController {
private final Logger logger = LoggerFactory.getLogger(getClass());
#Autowired
private MaintenanceInterceptor maintInterceptor;
#ModelAttribute()
public void globalAttributes(Model model, Locale locale) {
if (maintInterceptor.isMaintenanceWindowSet() && !maintInterceptor.isMaintenanceInEffect()) {
String msg = maintInterceptor.getImminentMaint(locale);
model.addAttribute("warningMaint", msg);
logger.debug("maint msg= " + msg);
}
}
}
A controller advice can be limited to certain controllers (not methods) by using one of the values of the #ControllerAdvice annotation, e.g.
#ControllerAdvice(assignableTypes = {MyController1.class, MyController2.class})
If you need to do it on a method level I suggest to take a look at Interceptors.
Thanks to #zeroflagL for pointing me to the interceptor solution. I ditched the #ControllerAdvice approach and ended up with this:
Custom annotation:
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface MaintAware {
String name() default "MaintAware";
}
Interceptor:
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod)handler;
Method method = handlerMethod.getMethod();
MaintAware maintAware = method.getAnnotation(MaintAware.class);
if (maintAware != null) {
Locale locale = request.getLocale();
if (isMaintenanceWindowSet() && !isMaintenanceInEffect()) {
String msg = getImminentMaint(locale);
if (!msg.isEmpty())
modelAndView.addObject("warningMaint", msg);
}
}
super.postHandle(request, response, handler, modelAndView);
}
Now I can annotate the specific methods that require the maintenance notification. Easy peasy. :)
A method in one of my Spring controller class,
#RequestMapping(value = "/products/{productId}/specifications", method = RequestMethod.GET)
public String setup(#PathVariable("productId") Integer pid, Model m) {
//...
m.addAttribute(foo); <-- error
return "my-page";
}
After I got an error message "Model object must not be null", I change the method signature as shown in the following:
public ModelAndView setup(#PathVariable("productId") Integer pid) {
//...
ModelAndView mv = new ModelAndView("my-page");
mv.addObject(foo); <-- error
return mv;
}
I was able to run the modified code once. But I got the same error on ModelAndView. I have used Spring MVC for many years. That is my first time having this problem. What is the cause?
I use Spring 4.0.6.RELEASE.
Although you have not provided the code that shows what the foo reference points to, it is safe to assume it is a null reference.
I took a look at the Project code on Github, and it is clear what happening here.
The ModelAndView#addObject(Object) method delegates to the ModelMap#addAttribute(Object) method, which asserts that the provided Object is not null, using the exact message your question is asking about.
ModelAndView method:
public ModelAndView addObject(Object attributeValue) {
getModelMap().addAttribute(attributeValue);
return this;
}
ModelMap method:
public ModelMap addAttribute(Object attributeValue) {
Assert.notNull(attributeValue, "Model object must not be null");
if (attributeValue instanceof Collection && ((Collection<?>) attributeValue).isEmpty()) {
return this;
}
return addAttribute(Conventions.getVariableName(attributeValue), attributeValue);
}
As I read explanation here, I found that Spring can automatically bind GET request parameter to a type. Below is the sample code from the link.
#Controller
#RequestMapping("/person")
public class PersonController {
...
#RequestMapping("/create")
public String create(Person p) {
//TODO: add Person to DAO
return "person/show";
}
}
Can someone tell me how spring do this? What bean that contains the logic to convert the parameter onto command type (Person type)?
The trick is done here: org.springframework.web.method.annotation.ModelAttributeMethodProcessor#resolveArgument()
This is the excerpt of code where it actually binds the class to the values:
String name = ModelFactory.getNameForParameter(parameter);
//Here it determines the type of the parameter and creates an instance
Object attribute = (mavContainer.containsAttribute(name)) ?
mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request);
//Then it binds the parameters from the servlet to the previously created instance
WebDataBinder binder = binderFactory.createBinder(request, attribute, name);
if (binder.getTarget() != null) {
bindRequestParameters(binder, request);
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors()) {
if (isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
}
My controller has following calls
#ModelAttribute("commandObject")
public UsersCommand getCommand(HttpServletRequest req) throws Exception {
...
return command;
}
#RequestMapping(value = {"addusers.json"}, method = RequestMethod.GET)
public void handleGet() {
//empty method
}
#RequestMapping(value = {"addusers.json"}, method = RequestMethod.POST)
public void handlePost(#ModelAttribute("commandObject") UsersCommand command, HttpServletRequest req) throws Exception {
//do stuff
}
during the get i get the json of my UsersCommand object, however after I do a post, I am getting the json of my command object which i do not need as i want to do a fire and forget post.
How can I avoid the json object from coming down to browser during post?
It seems like you only want JSON returned with the GET method. To do this, remove the #ModelAttribute method all-together. This tells Spring to add the return object to your model on every handler in the controller, which you don't want. Then modify your GET handler to be like the following:
#RequestMapping(value = {"addusers.json"}, method = RequestMethod.GET)
#ResponseBody
public UserCommand handleGet() {
UserCommand cmd = getUserCommand();
return cmd;
}
The #ResponseBody annotation tells Spring to serialize the return type to JSON (or XML if you annotated your class with JAXB, and depending on the request accept headers). For this to work, you also need to add Jackson to your classpath and make sure you're using <mvc:annotation-driven /> or #EnableWebMvc in your XML or Java config, respectively.
This will sound like self-promotion, but I wrote a post about this if you want more detail: http://codetutr.com/2013/04/09/spring-mvc-easy-rest-based-json-services-with-responsebody/.
Hope that's helpful! Let me know if I can add any more clarity for you.