Spring Controller Test - mockMvc parameter null - spring

I try to test a controller and have a problem because of a parameter.
I recieve an error:
"cannot invoke "java.lang.Integer.intValue()" because "voivodeship" is
null"
It shouldn't be null. because it is an id of last selected voivodeship(option from dropdown list). I think the problem is here .requestAttr("voivodeship", 10). How can i pass this parameter using mockMvc?
#Test
public void selectVoivodeshipTest() throws Exception { //post
Integer voivodeship = 10;
List<Voivodeship> voivodeships = voivodeshipService.findAll();
List<City> cities = cityService.getAllCitiesByVoivodeship(voivodeship);
mockMvc.perform(MockMvcRequestBuilders.post("/select_voivodeship")
.contentType(MediaType.APPLICATION_JSON)
.requestAttr("voivodeship", 10)
.content(new Gson().toJson(voivodeships)))
.andExpect(model().attribute("voivodeships", voivodeships))
.andExpect(model().attribute("voivodeship_selected", voivodeship))
.andExpect(model().attribute("cities", cities))
.andExpect(model().hasNoErrors())
.andExpect(view().name("/taxoffice"))
.andExpect(status().isOk());
}
Controller.java
#RequestMapping(value="/select_voivodeship", method = RequestMethod.POST)
public String selectVoivodeship (int voivodeship, Model model) {
List<Voivodeship> voivodeships = voivodeshipService.findAll();
model.addAttribute("voivodeships", voivodeships);
model.addAttribute("voivodeship_selected", voivodeship);
List<City> cities = cityService.getAllCitiesByVoivodeship(voivodeship);
model.addAttribute("cities", cities);
return "taxoffice";
}

I think that the issue is that in your controller you still have int type for voivodeship, which cannot be null. Change it to Integer to match what you have in your test.
public String selectVoivodeship (Integer voivodeship, Model model) {
Objects can be null, primitive types cannot.

Related

Spring MVC - Binding results missing after manually overriding model

I've got some code which looks like this
#Controller
public class FooController {
#RequestMapping(value = "/foos", method = POST)
public String createFoo(#ModelAttribute("FooDto")
#Valid FooDto fooDto, BindingResult bindingResult, Model model) {
var foo = fooMapper.toFooModel(fooDto);
if (bindingResult.hasErrors()) {
var fooDto1 = fooMapper.toFooDto(foo);
model.addAttribute("FooDto", fooDto1); // binding result disappears
...
}
...
Also some Test for my Controller class which looks like the following:
#Test
void createFooWithValidationError() throws Exception {
perform(post("/foos")).andExpect(status().isOk())
.andExpect(model().attribute("foo", notNullValue()))
.andExpect(model().errorCount(1)); // this fails, no error/binding result exists!
}
The test has not been successfull, because there is no binding result after setting
model.addAttribute("FooDto", fooDto1);.
I know, that i could set
model.addAttribute("FooDto", fooDto);
whereby the binding result does not disappear.
However I wonder why the binding result is disappearing. Is there some spring magic which binds the binding result to the concrete instance of my model attribute? Is there some other workaround to make the above code working?
I'll dived into the spring code and found my answer:
model.addAttribute("FooDto", fooDto1); does call
public ModelMap addAttribute(String attributeName, #Nullable Object attributeValue) {
Assert.notNull(attributeName, "Model attribute name must not be null");
put(attributeName, attributeValue); // calls BindingAwareModelMap
return this;
}
public class BindingAwareModelMap {
public Object put(String key, #Nullable Object value) {
removeBindingResultIfNecessary(key, value);
return super.put(key, value);
}
private void removeBindingResultIfNecessary(Object key, #Nullable Object value) {
...
if (bindingResult != null && bindingResult.getTarget() != value) {
remove(bindingResultKey); // Removes the binding result if target refernces to other instance
}
}
}
As u can see if the binding results references to an other instance the binding result entry in ModelMap is removed.

Spring Boot/Java Mapping Enum Values to RequestParam [duplicate]

This question already has answers here:
Spring's #RequestParam with Enum
(9 answers)
Closed 3 years ago.
I have an Enum like below
public enum Customer {
RETAIL("retail"),
FREELANCER("FreeLancer"),
MARKET("market"),
PUBLICATION("publication");
private String contentType;
private static final Map<String,Customer> contentTypeMap;
public String getContentType(){
return this.contentType;
}
static {
Map<String,Customer> map = new ConcurrentHashMap<>();
for(Customer type : Customer.values ()){
map.put (type.getContentType (),type);
}
contentTypeMap = map;
}
Customer(String contentType){
this.contentType=contentType;
}
public static Customer getContentType(String contentType){
return contentTypeMap.get (contentType);
}
}
This enum represents the type of customer.
We have an API that return the customer details
#RequestMapping(value="/getData", method=RequestMethod.GET, produces="application/json")
public BatchResponse triggerBatchJob(
#RequestParam(value="updateFrom", required=false) #DateTimeFormat(pattern="yyyyMMdd") String updateFrom,
#RequestParam(value="updateTo", required=false) #DateTimeFormat(pattern="yyyyMMdd") String updateTo,
#RequestParam(value="customerType") (VALIDATE_HERE)String customerType) {
// ...
}
I need to validate the customerType value to be the ones present in the Enum, Is there a way to validate the same with the method declaration as I have done in the case of date rather than method body by using getContentType method or something.
Please help.
Change your method to following:
#RequestMapping(value="/getData", method=RequestMethod.GET, produces="application/json")
public BatchResponse triggerBatchJob(
#RequestParam(value="updateFrom", required=false) #DateTimeFormat(pattern="yyyyMMdd") String updateFrom,
#RequestParam(value="updateTo", required=false) #DateTimeFormat(pattern="yyyyMMdd") String updateTo,
#RequestParam(value="customerType") CustomerType customerType) {
// ...
}
i.e. customerType type should be CustomerType not String. Now only values those match enum will be mapped.
Note:- The values will have to be provided is specific format i.e. enum name itself e.g. in your case FREELANCER,RETAIL, PUBLICATION etc values should be passed in request.
Edit
As requested by OP below is customizing the enum handling from String:
Add #initBinder in the controller and add following method:
#InitBinder
public void initBinder(final WebDataBinder webdataBinder) {
webdataBinder.registerCustomEditor(Customer.class, new CustomerConverter());
}
and declare a converter class as below:
import java.beans.PropertyEditorSupport;
public class CustomerConverter extends PropertyEditorSupport{
public void setAsText(final String text) throws IllegalArgumentException {
System.out.println("--->"+Customer.getContentType(text));
setValue(Customer.getContentType(text));
}¡¡
}
Added System.out.println to show that value is interpreted and printed as expected.
A simple null check will do
Customer customer = Customer.getContentType(customerType);
if (customer == null) {
throw new Exception("Invalid Customer type");// use validation exception
}

Spring MVC #RequestParam a list of objects

I want to create a page where a person sees a list of users and there are check boxes next to each of them that the person can click to have them deleted.
In my MVC that consumes a REST API, I want to send a List of User objects to the REST API.
Can the #RequestParam annotation support that?
For example:
#RequestMapping(method = RequestMethod.DELETE, value = "/delete")
public #ResponseBody Integer delete(
#RequestParam("users") List<Users> list) {
Integer deleteCount = 0;
for (User u : list) {
if (u != null) {
repo.delete(u);
++deleteCount;
}
}
return deleteCount;
}
In the MVC client, the url would be:
List list = new ArrayList<User>();
....
String url = "http://restapi/delete?users=" + list;
Request parameters are a Multimap of String to String. You cannot pass a complex object as request param.
But if you just pass the username that should work - see how to capture multiple parameters using #RequestParam using spring mvc?
#RequestParam("users") List<String> list
But I think it would be better to just use the request body to pass information.
Spring mvc can support List<Object>, Set<Object> and Map<Object> param, but without #RequestParam.
Take List<Object> as example, if your object is User.java, and it like this:
public class User {
private String name;
private int age;
// getter and setter
}
And you want pass a param of List<User>, you can use url like this
http://127.0.0.1:8080/list?users[0].name=Alice&users[0].age=26&users[1].name=Bob&users[1].age=16
Remember to encode the url, the url after encoded is like this:
http://127.0.0.1:8080/list?users%5B0%5D.name=Alice&users%5B0%5D.age=26&users%5B1%5D.name=Bob&users%5B1%5D.age=16
Example of List<Object>, Set<Object> and Map<Object> is displayed in my github.
Just a reminder, any List of custom objects might require custom converters to be registered, like:
#Bean
public Converter<String, CustomObject> stringToCustomObjectConverter() {
return new Converter<>() {
#Override
public CustomObject convert(String str) {
return new ObjectMapper().readValue(str, CustomObject.class);
}
};
}
#Bean
public Converter<String, List<CustomObject>> stringToListCustomObjectConverter() {
return new Converter<>() {
#Override
public List<CustomObject> convert(String str) {
return new ObjectMapper().readValue(str, new TypeReference<>() {
});
}
};
}
So you can cover custom cases like:
/api/some-api?custom={"name":"Bla 1","age":20}
/api/some-api?custom={"name":"Bla 1","age":20}&custom={"name":"Bla 2","age":30}
/api/some-api?custom=[{"name":"Bla 1","age":20},{"name":"Bla 2","age":30}]
where: #RequestParam("custom") List customObjects

Is the view name inferred from the request path or from the string returned from the request handling method in Spring4?

First, have a look at where the jsp is residing, /WEB-INF/views/
Case a) with RequestParam
Consider the snippet - controller class:
#Controller
public class SpittleController {
private SpittleRepository spittleRepository;
#Autowired
public SpittleController(SpittleRepository spittleRepository) {
this.spittleRepository = spittleRepository;
}
#RequestMapping(value = "/spittles", method = RequestMethod.GET)
public String spittles(Model model, #RequestParam("max") long max,
#RequestParam("count") int count) {
model.addAttribute("spittleList",spittleRepository.findSpittles(max, count));
//return "spittles";
return null; // Note this, I returned NULL
}
}
And I see the result as:
Now consider returning a String than a null,
#RequestMapping(value = "/spittles", method = RequestMethod.GET)
public String spittles(Model model, #RequestParam("max") long max,
#RequestParam("count") int count) {
model.addAttribute("spittleList",spittleRepository.findSpittles(max, count));
return "spittles";
}
and the O/P reflected as:
Caseb) with PathVariable
#RequestMapping(value = "/spittles/{spittleId}", method = RequestMethod.GET)
public String spittles(Model model, #PathVariable("spittleId") long spittleId) {
System.out.println("HERE: "+spittleId);
model.addAttribute("spittleList",spittleRepository.findOne(spittleId));
return null;
}
I returned null as shown above.
O/P reflected as:
First of all, what is this as shown in the above figure:
/WEB-INF/views/spittles/1.jsp
Now I returned the string "spittles" and it starts to work fine.
Can someone tell me why the difference in case of RequestParam and PathVariable, both for null and "spittles" returned values?
I see myself struggled a lot when it comes to URL resolution. Any suggestions to guide me on how to work with URL's would be very nice?
The question is not asking for how the view name gets resolved but rather why the difference in case of #RequestParam and #PathVariable?

Spring #PathVariable integration test

I'm trying ot write an integration test for one of my methods, that handles a form submission.
The problem is that i use #PathVariable int id in the #RequestMapping of my controller.
I get the following error message when my test method reaches the .handle() part.
nested exception is java.lang.IllegalStateException: Could not find #PathVariable [id] in #RequestMapping
So the controller cant reach the id. I'm using request.setRequestURI("/url-" + s.getId()); But appareantly its not helping with setting up #PathVariable.
Is there any way to set up the #PathVariable of my Mock object?
Update:
Yeah, im useing MockHttpServletRequest and annotationMethodHandlerAdapter.handle in my test class.
The Controller:
#RequestMapping(method = RequestMethod.POST, params = {"edit" })
public String onSubmit(#ModelAttribute("sceneryEditForm") SceneryEditForm s,
#PathVariable int id, Model model) {
// some code that useing id
}
The test method:
#Test
public void testEditButton() throws Exception {
MyObject s = new MyObject();
request.setMethod("POST");
request.setRequestURI("/edit-" + s.getId());
request.setParameter("edit", "set");
final ModelAndView mav = new AnnotationMethodHandlerAdapter()
.handle(request, response, controller);
assertViewName(mav, "redirect:/view-" + s.getId());
}
The error is correct: there is no path variable id
You need to add the path expression with an placeholder id
#RequestMapping(value = "/something/{id}",
method = RequestMethod.POST,
params = {"edit" })
public String onSubmit(#ModelAttribute("sceneryEditForm") SceneryEditForm s,
#PathVariable int id, Model model) {
// some code that useing id
}

Resources