I am writing unittests and I've stumbled across something I can't find a solution for that fits my needs or code I already have.
The User first comes at a page where they have to choose (from a dropdown list) what brand they want to make a configuration for. After they click 'submit', it takes them to a page where all the appropriate settings are listed per category.
Now, the choosing of the brand is a form and it's submitted to this method:
// Display a form to make a new Configuration
#PostMapping("/addConfig")
public String showConfigurationForm(WebRequest request, Model model) {
// Get the ID of the selected brand
Map<String, String[]> inputMap = request.getParameterMap();
for (Entry<String, String[]> input : inputMap.entrySet()) {
if (input.getValue().length > 0
&& input.getKey().startsWith("brand")) {
brandId = Integer.parseInt(input.getValue()[0]);
}
}
// Load the view
model.addAttribute("categoryResult",
databaseService.getCategories(brandId));
model.addAttribute("configItemsMap",
databaseService.getAddConfigItems(brandId));
return "addConfig";
}
I want to unittest this method to see if the model has the attributes we expect it to.
This is the unit test I have now:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
#ActiveProfiles("test")
public class AddConfigurationTest {
#Autowired
AddConfigurationController addConfigurationController;
#MockBean
DatabaseService databaseServiceTest;
#Mock
WebRequest webRequest;
#Before
public void setup() {
// Make Categories
List<ItemCategory> defaultCategories = new ArrayList<>();
defaultCategories.add(new ItemCategory(1, 1, "GPS settings"));
// Mock it
Mockito.when(this.databaseServiceTest.getCategories(1)).thenReturn(
defaultCategories);
}
#Test
public void configurationFormShouldContainCategories() {
// TODO: Still needs param for webrequest
// Make a model
Model model = new ExtendedModelMap();
addConfigurationController.showConfigurationForm(webRequest, model);
// Get the list from the model
#SuppressWarnings("unchecked")
List<ItemCategory> categoryList = (List<ItemCategory>) model.asMap()
.get("categoryResult");
System.out.println(categoryList);
}
}
The System.out.println now outputs: []
I am sure it has to do with the WebRequest, because as I have it now this WebRequest does not have the input from a form the showConfigurationForm method needs.
My question is: how can I add the right data to WebRequest so the test will return a List? Or is there another way around that I have not figured out?
Just configure your Mock WebRequest object before executing the test:
#Before
public void setup()
{
Map<String, String[]> mockParameterMap = new HashMap<>();
mockParameterMap.put("brand00", new String[]{"value01"});
// add all the parameters you want ...
Mockito.when(webRequest.getParameterMap())
.thenReturn(mockParameterMap);
}
That should be enough for the example you described.
It is also possible to use MockHttpServletRequest.html to set-up the http request in the way you want.
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
servletRequest.setServerName("www.example.com");
servletRequest.setRequestURI("/v1/someuri");
servletRequest.addParameter("brand1", "value1");
servletRequest.addParameter("brand2", "value2");
servletRequest.addParameter("another-param", "another-value");
ServletWebRequest servletWebRequest = new ServletWebRequest(servletRequest);
assertThat("brand names ", servletWebRequest.getParameterMap(), hasEntry("brand1", "value1"));
It is particularly useful in cases such as ServletWebRequest whose 'getRequestURI()' method, for instance, is a final method and hence cannot be mocked. So, instead of mocking we can simply pass in a web request that we built, like the one above.
Related
I need to know how Spring boot maps the request parameters in the URL to a POJO at run time.
Here is an example URL with parameters
http://localhost:8080/api/public/properties?serviceType.in=SALE&title.contains=some text&price.greaterOrEqualThan=500&price.lessOrEqualThan=50000&propertyType.in=HOUSE&locationId.in=1,2&landSize.greaterOrEqualThan=100&landSize.lessOrEqualThan=1000&bedrooms.greaterOrEqualThan=2&bedrooms.lessOrEqualThan=5&bathrooms.greaterOrEqualThan=1&bathrooms.lessOrEqualThan=3&ageType.in=BRAND_NEW
I have a number of Criteria classes that all extends PropertyCriteria class. To give an example, if the request contains no parameters, I want the controller to use the PropertyCriteria. If the request contains a bedrooms parameter, I want the controller to use the HousePropertyCriteria and so on. See controller method example below.
#GetMapping("/public/properties")
public ResponseEntity<List<Property>>
getAllPropertiesNested(HttpServletRequest request) {
if (condition1 == true) {
EntityOnePropertyCriteria c1 = new EntityOnePropertyCriteria();
//populate c1 using request object
} else {
EntityTwoPropertyCriteria c2 = new EntityTwoPropertyCriteria();
//populate c2 using request object
}
}
Two ways of doing this manually:
1) I wonder if in your project you have access to HttpServletRequest object. If that is the case, you can use the method request.getParameter(nameParam) to populate the object that you need.
2) Use beanutils library and using the method
BeanUtils.copyProperties(dest, source)
Using "#RequestParam Map source" in your controller and replacing the dest object you want fill
I found the answer on this link.
public static void applyMapOntoInstance(Object instance, Map properties) {
if (properties != null && !properties.isEmpty()) {
BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(instance);
beanWrapper.setAutoGrowNestedPaths(true);
for (Iterator<?> iterator = properties.entrySet().iterator(); iterator.hasNext();) {
Map.Entry<String, ?> entry = (Map.Entry<String, ?>) iterator.next();
String propertyName = entry.getKey();
if (beanWrapper.isWritableProperty(propertyName)) {
beanWrapper.setPropertyValue(propertyName, entry.getValue());
}
}
}
}
My controller method now looks like:
#GetMapping("/public/properties")
#Timed
public ResponseEntity<List<Property>> getAllPropertiesNested(HttpServletRequest request) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
HttpHeaders headers = null;
if (requestContains("bedrooms", request)) {
HousePropertyCriteria housePropertyCriteria = new HousePropertyCriteria();
applyMapOntoInstance(housePropertyCriteria, request.getParameterMap());
Page<HouseProperty> page = housePropertyQueryService.findByCriteriaNested(housePropertyCriteria, pageable);
headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/public/properties");
return new ResponseEntity(page.getContent(), headers, HttpStatus.OK);
} else {
Page<Property> page = propertyQueryService.findByCriteriaNested(criteria, pageable);
headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/public/properties");
return new ResponseEntity(page.getContent(), headers, HttpStatus.OK);
}
}
I have a Spring Boot application that uses Spring MVC in the usual manner, with a bunch of #RequestMapping methods, Freemarker definitions, and the like. This is all tied together with a WebMvcConfigurerAdapter class.
I'd like to provide a service where the user submits a list of valid URLs, and the webapp would work out which controller would be called, passes in the parameters, and returns a combined result for every URL — all in one request.
This would save the user from having to make hundreds of HTTP calls, but would still allow them to make one-off requests if need be. Ideally, I'd just inject an auto-configured Spring bean, so I don't have to repeat the URL resolving and adapting and handling that Spring does internally, and the controller's list of other controllers would never go out of sync with the real list of controllers.
I expected to write something like this (simplified to only deal with one URL, which is pointless but easier to understand):
#Autowired BeanThatSolvesAllMyProblems allMappings;
#PostMapping(path = "/encode", consumes = MediaType.TEXT_PLAIN_VALUE)
#ResponseBody
public String encode(#RequestBody String inputPath) {
if (allMappings.hasMappingForPath(inputPath)) {
return allMappings.getMapping(inputPath).execute();
} else {
return "URL didn't match, sorry";
}
}
Instead, I've had to define Spring beans I don't know what they do and have been repeating some of what Spring is meant to do for me, which I'm worried won't work quite the same as it would if the user just made the call themselves:
// these two are #Beans, with just their default constructor called.
#Autowired RequestMappingHandlerMapping handlers;
#Autowired RequestMappingHandlerAdapter adapter;
#PostMapping(path = "/encode", consumes = MediaType.TEXT_PLAIN_VALUE)
#ResponseBody
public String encode(#RequestBody String inputText) {
final HttpServletRequest mockRequest = new MockHttpServletRequest(null, inputText);
final StringBuilder result = new StringBuilder();
this.handlers.getHandlerMethods().forEach((requestMappingInfo, handlerMethod) -> {
if (requestMappingInfo.getPatternsCondition().getMatchingCondition(mockRequest) != null) {
try {
final MockHttpServletResponse mockResponse = new MockHttpServletResponse();
result.append("Result: ").append(adapter.handle(mockRequest, mockResponse, handlerMethod));
result.append(", ").append(mockResponse.getContentAsString());
result.append("\n");
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
});
return result.toString();
}
I thought I was doing quite well going down this path, but it's failing with Missing URI template variable errors, and not only do I have no idea how to put the request parameters in (another thing which Spring could be able to handle itself), but I'm not even sure that this is the right way to go about doing this. So how do I simulate a Spring MVC request "reflectively", from within the webapp itself?
JSON API spec. solves this problem by allowing sending multiple operations per request. There even exists a quite mature implementation that supports this feature which is called Elide. But I guess this is might not fully meet your requirements.
Anyway, here's what you can do.
You have to take into consideration that DispatcherServlet holds handlerMappings list that is used to detect appropriate request handler and handlerAdaptors. The selection strategy for both lists is configurable (see DispatcherServlet#initHandlerMappings and #initHandlerAdapters).
You should work out a way you would prefer to retrieve this lists of handlerMappings/initHandlerAdapters and stay in sync with DispatcherServlet.
After that you can implement your own HandlerMapping/HandlerAdaptor (or present a Controller method as in your example) that would handle the request to /encode path.
Btw, HandlerMapping as javadoc says is
Interface to be implemented by objects that define a mapping between
requests and handler objects
or simply saying if we take DefaultAnnotationHandlerMapping that would map our HttpServletRequests to #Controller methods annotated with #RequestMapping. Having this mapping HandlerAdapter prepares incoming request to consuming controller method, f.ex. extracting request params, body and using them to call controller's method.
Having this, you can extract URLs from main request, create a list of stub HttpRequests holding the information needed for further processing and loop through them calling this:
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
having a handlerMapping you call
HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
and then you can finally call
ha.handle(processedRequest, response, mappedHandler.getHandler());
which in turn would execute the controller's method with params.
But having all this, I would not recommend to following this approach, instead, think about usage of JSON API spec or any other.
How about using Springs RestTemplate as client for this? You could call your controllers within the spring controller as if it would be an external resource:
#ResponseBody
public List<String> encode(#RequestBody List inputPaths) {
List<String> response = new ArrayList<>(inputPaths.size());
for (Object inputPathObj : inputPaths) {
String inputPath = (String) inputPathObj;
try {
RequestEntity.BodyBuilder requestBodyBuilder = RequestEntity.method(HttpMethod.GET, new URI(inputPath)); // change to appropriate HttpMethod, maybe some mapping?
// add headers and stuff....
final RequestEntity<Void> requestEntity = requestBodyBuilder.build(); // when you have a request body change Void to e.g. String
ResponseEntity<String> responseEntity = null;
try {
responseEntity = restTemplate.exchange(requestEntity, String.class);
} catch (final HttpClientErrorException ex) {
// add your exception handling here, e.g.
responseEntity = new ResponseEntity<>(ex.getResponseHeaders(), ex.getStatusCode());
throw ex;
} finally {
response.add(responseEntity.getBody());
}
} catch (URISyntaxException e) {
// exception handling here
}
}
return response;
}
Note that generic do not work for the #RequestBody inputPaths.
See alse http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html and https://spring.io/guides/gs/consuming-rest/ .
I agree with the other answers that you should consider this feature outside of your project, instead of having it in the code. It is a question of design and you can choose the approach you want. Based on your comment that these are GET requests, you can achieve what you want with a request dispatcher to trigger your requests within your special Controller service method for each URL and capture the response with a HttpServletResponseWrapper instance.
In the following code sample, the "consolidate" method takes comma separated URLs like this ("http://localhost:8080/index/index1,index2", here "index1,index2" is the URL list), consolidates their text output into a single payload and returns it. For this example URL, the consolidated outputs of http://localhost:8080/index1 and http://localhost:8080/index2 will be returned. You might want to extend/modify this with added parameters, validation, etc for the URLs. I tested this code with Spring Boot 1.2.x.
#Controller
public class MyController {
#RequestMapping("/index/{urls}")
#ResponseBody
String consolidate(#PathVariable String[] urls, HttpServletRequest request, HttpServletResponse response) {
StringBuilder responseBody = new StringBuilder();
//iterate for each URL provided
for (String url : urls) {
RequestDispatcher dispatcher = request.getServletContext().getRequestDispatcher("/" + url);
HttpServletResponseWrapper wrapper = new HttpServletResponseWrapper((HttpServletResponse) response) {
private CharArrayWriter output = new CharArrayWriter();
#Override
public PrintWriter getWriter() {
return new PrintWriter(output);
}
#Override
public String toString() {
return output.toString();
}
};
try {
dispatcher.include(request, wrapper);
//append the response text
responseBody.append(wrapper.toString());
} catch (ServletException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//This holds the consolidated output
return responseBody.toString();
}
#RequestMapping("/index1")
String index1() {
return "index1";
}
#RequestMapping("/index2")
String index2() {
return "index2";
}
}
I am writing a JUnit test case for a short method and I think I am setting all the required fields to prevent any NullPointerException but filters is always Null due to which my test doesn't go inside the loop. ILocalPostProcessFilter is an interface that is implemented by BaseExceptionPostProcessFilter which is an abstract class and this abstract class is extended by a concrete class MessageToExceptionPostProcessFilter. I don't have any asserts yet so please ignore it.
Method under test:
protected <T extends BaseServiceResponse> T processLocalPostProcessFilters(T resp, Response cResp) throws EISClientException
{
List<ILocalPostProcessFilter> filters = config.getLocalPostProcessFilters();
if(filters == null)
return resp;
T bResp = resp;
for(ILocalPostProcessFilter filter : filters)
{
bResp = filter.postProcess(resp, cResp);
}
return bResp;
}
JUnit test:
#Test
public void testProcessLocalPostProcessFilters() throws Exception {
TestClass baseClient = new TestClass(new ClientConfiguration(), "serviceURL");
CatastropheServiceResponse catastropheServiceResponse = new CatastropheServiceResponse();
CatastropheResponse entity = new CatastropheResponse();
catastropheServiceResponse.setContentLength(10);
catastropheServiceResponse.setContentType(ContentType.XML);
entity.setSource(ResponseSourceEnum.CATASTROPHE);
entity.setTransactionId("01234");
catastropheServiceResponse.setEntity(entity);
Response response = mock(Response.class);
ILocalPostProcessFilter filter = new MessageToExceptionPostProcessFilter();
((BaseExceptionPostProcessFilter) filter).setHttpStatusCode("200");
((BaseExceptionPostProcessFilter) filter).setCondition(ConditionOperator.OR);
List<ILocalPostProcessFilter> listFilter = new ArrayList<>();
listFilter.add(filter);
ClientConfiguration clientConfiguration = new ClientConfiguration();
clientConfiguration.setLocalPostProcessFilters(listFilter);
baseClient.processLocalPostProcessFilters(catastropheServiceResponse, response);
}
Not sure what needs to be done to populate filters.
Any help would be appreciated.
Thanks
I am getting an unexpected error java.io.IOException: Stream closed
Error starts from this line
public void createPo(String username, PurchaseOrderDto po, List<PoItemDto> items) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("poDto", po);
params.put("items", items);
rc.post(String.format("/po?username=%s", username), params);
}
rc is the rest client which calls the api from another server. Here is its code of its post function
protected void post(String path, Object object) {
restTemplate.postForObject((url + path), object, Void.class);
}
Following is the api which handles the above request PoController.java
#ResponseStatus(HttpStatus.OK)
#RequestMapping(value = "/po", method = RequestMethod.POST)
public void createPo(#RequestParam("username") String buyerId, #RequestBody PurchaseOrderDto poDto, #RequestBody List<PoItemDto> items) {
poService.createPo(buyerId, poDto, items);
}
Logs from the tomcat server handling the request:
[http-bio-9080-exec-6] ERROR ib.pms.controller.PoController - Stream closed java.io.IOException: Stream closed
at org.apache.catalina.connector.InputBuffer.readByte(InputBuffer.java:301)
at org.apache.catalina.connector.CoyoteInputStream.read(CoyoteInputStream.java:106)
at java.io.FilterInputStream.read(FilterInputStream.java:83)
at java.io.PushbackInputStream.read(PushbackInputStream.java:139)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:168)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:105)
I can't make any sense out of this.
Is there a cleaner way to pass parameters to the restApi?
You have two #RequestBody, it's not possible, you should have one object Foo (implementing Serializable) containing PurchaseOrderDto po, List<PoItemDto> items and then passing Foo in your restTemplate.postForObject.
So, your controller has to be changed for something like:
#ResponseStatus(HttpStatus.OK)
#RequestMapping(value = "/po", method = RequestMethod.POST)
public void createPo(#RequestParam("username") String buyerId, #RequestBody Foo foo) {
poService.createPo(buyerId, foo.getDoDto(), foo.getItems());
}
I'm not sure it's going to fix your issue but at least it's one problem less
If you want to send multiple objects, you still can use a List: #RequestBody List<Foo> foos
i am mocking a HttpServletRequest , in servlet call there are new values getting set in request because using same request we are dispaching request to some jsp so request object is used as a input object to servlet as well as output for next page.
i mocked all input parameters , but for all request.setAttribute() , my code is doing nothing as it's a mocked class , say if i have
request.setAttribute(a,"10")
System.out.println("a = " + request.getAttribute("a"));
i get null cuz i haven't given any behavious for Request.getAttribute("a") , and i can't , it's my response for next page , so that explain i need 2 behaviour my request object thus partial mocking , and i am unable to spy or do any partial mocking on it so far. any ideas?
Code :
//Testcase
Myservlet.java
public void doPost(request,response)
{
String a = request.getAttribute("a");
String b = request.getAttribute("b");
int sum = Integer.parseInt(a) + Integer.parseInt(b);
request.setAttribute("sum",sum);
//well in this example i can use sum what i calculated but in real senario i can't , i have to use request.getAttribute("sum")
insertSumIntoDB(request.getAttribute("sum"));
}
}
//testMyservlet.java
#test
public void testServlet()
{
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
when(request.getAttribute(a)).thenReturn("10");
when(request.getAttribute(b)).thenReturn("20");
new Myservlet(request,response);
}
Spring's MockHttpServletRequest and MockHttpServletResponse are useful for that purpose. E.g.
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
request.addHeader(HttpHeaders.HOST, "myhost.com");
request.setLocalPort(PORT_VALID); // e.g. 8081
request.setRemoteAddr(REQUEST_IP); // e.g. 127.0.0.1
then I can call myclass.method(request, response, ...) and check whether some attribute has been correctly set into the request, e.g.
MyBean attr = (MyBean) request.getAttribute(ATTRIBUTE_NAME));
// do my Assert.* stuff with 'attr'
MockHttpServletRequest and MockHttpServletResponse work fine where mock(HttpServletRequest.class) fails, for instance where you need to get back the real content of a request attribute which has been previously set within your business logic.
You need to store attributes into a collection :
// Collection to store attributes keys/values
final Map<String, Object> attributes = new ConcurrentHashMap<String, Object>();
// Mock setAttribute
Mockito.doAnswer(new Answer<Void>() {
#Override
public Void answer(InvocationOnMock invocation) throws Throwable {
String key = invocation.getArgumentAt(0, String.class);
Object value = invocation.getArgumentAt(1, Object.class);
attributes.put(key, value);
System.out.println("put attribute key="+key+", value="+value);
return null;
}
}).when(request).setAttribute(Mockito.anyString(), Mockito.anyObject());
// Mock getAttribute
Mockito.doAnswer(new Answer<Object>() {
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
String key = invocation.getArgumentAt(0, String.class);
Object value = attributes.get(key);
System.out.println("get attribute value for key="+key+" : "+value);
return value;
}
}).when(request).getAttribute(Mockito.anyString());
Mockito supports real partial mocks: Real partial mocks (Since 1.8.0)
I think it fits your needs