I have the following controller which accept input as #RequestParam
#RequestMapping(value = "/fetchstatus", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public Response fetchStatus(
#RequestParam(value = "userId", required = true) Integer userId) {
Response response = new Response();
try {
response.setResponse(service.fetchStatus(userId));
response = (Response) Util.getResponse(
response, ResponseCode.SUCCESS, FETCH_STATUS_SUCCESS,
Message.SUCCESS);
} catch (NullValueException e) {
e.printStackTrace();
response = (Response) Util.getResponse(
response, ResponseCode.FAILED, e.getMessage(), Message.ERROR);
} catch (Exception e) {
e.printStackTrace();
response = (Response) Util.getResponse(
response, ResponseCode.FAILED, e.getMessage(), Message.ERROR);
}
return response;
}
I need a unit test class for this and I am beginner with spring mvc. I don't know writing test classes with #RequestParam as input.
Any help will be appreciated ..
I solved this issue. I just changed the url. Now it contains the parameter as below in test class:
mockMvc.perform(get("/fetchstatus?userId=1").andExpect(status().isOk());
You can use MockMvc for testing Spring controllers.
#Test
public void testControllerWithMockMvc(){
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controllerInstance).build();
mockMvc.perform(get("/fetchstatus").requestAttr("userId", 1))
.andExpect(status().isOk());
}
Also, it is possible to do it using pure JUnit, as long as you need to test only the logic inside your class
#Test
public void testControllerWithPureJUnit(){
Controller controller = new Controller();
//do some mocking if it's needed
Response response = controller.fetchStatus(1);
//asser the reponse from controller
}
Related
Controller:
#ApiOperation(value = " update record", response = APIResponse.class)
#ApiResponses(value = {#ApiResponse(code =200, message = "OK"),
#ApiResponses(value = {#ApiResponse(code =500, message = "Internal server error"),
#ApiResponses(value = {#ApiResponse(code =404, message = "NO_RECORD")})
#PutMapping(value = "/update/{id}")
#ResponseBody
public ResponseEntity<APIResponse> updateRecord(HttpServletRequest request, #RequestBody RecordDTO input, #PathVariable(value="id") int code){
APIResponse response = null;
try{
response = service.updateRecord(code, input);
}
catch(JSONException e){
log.error("Error Parsing JSON");
response = new APIResponse(HttpStatus.INTERNAL_SERVER_ERROR, ERROR_JSON_PARSING, ERROR);
}
return new ResponseEntity<>(response, HttpStatus.OK);
}
my test case foor controller:
#Test
public void update() throws Exception{
RecordDTO recordDto = new RecordDTO();
Object mapper = new ObjectMapper();
String value = mapper.writeValueAsString(StationDTO);
given(service.updateRecord(anyInt(), any(RecordDTO.class))).willThrow(JSONException.class);
mockMvc.perform(put(baseUrl + "/update/12")
.contentType(MediaType.APPLICATION_JSON).content(value))
.andExpect(status().isInternalservererror())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status",Matchers.is("INTERNAL_SERVER_ERROR")))
.andExpect(jsonPath("$.message",Matchers.is("ERROR_JSON_PARSING")))
.andExpect(jsonPath("$.resposeStatus",Matchers.is("ERROR")));
APIResponse response = new APIResponse(HttpStatus.OK, SUCCESS, SUCCESS, null);
given(service.updateRecord(anyInt(), any(RecordDTO.class))).willReturn(response);
mockMvc.perform(put(baseUrl + "/update/12")
.contentType(MediaType.APPLICATION_JSON).content(value))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.status",Matchers.is("OK")))
.andExpect(jsonPath("$.message",Matchers.is("SUCCESS")))
.andExpect(jsonPath("$.resposeStatus",Matchers.is("SUCCESS")));
}
DTO:
public class RecordDTO{
private String id;
private String name;
private String number;
}
I am getting java.lang assertion error expected 500 but was 200. I don't what is wrong with the test case.. Is there any other way to write the test case? Also can you recommend any platform from where i can gain knowledge of how to write test cases then do comment down. Thanks for the help!
Seems like your mocked service is not injecting into your controller.
Alternative solution (I assume you use Spring-Boot):
DisableAutowireRequireInitializer. This will prevent to load all dependencies inside your Controller.
Create inside your ControllerTest inner class: private static ServiceImplMock entends ServiceImpl
Now, override updateRecord method inside ServiceMock to do your testing cases
#Override
public APIResponse updateRecord(int code, RecordDTO input) throws JSONException {
if(code == 12) throw new JSONException(...)
else your_business_logic
}
Now, add this ServiceImplMock into your #SpringBootTest
#SpringBootTest(classes = {
Controller.class,
ControllerTest.ServiceImplMock.class,
...
})
#AutoConfigureMockMvc
#ContextConfiguration( initializers = {DisableAutowireRequireInitializer.class })
class ControllerTest {
Now, your test cases should work (Remove given(...).willThrow(...); since we don't need it anymore)
Also can you recommend any platform from where i can gain knowledge of how to write test cases then do comment down
https://www.baeldung.com/junit
https://www.baeldung.com/spring-boot-testing
https://mkyong.com/spring-boot/spring-boot-junit-5-mockito/
I have the following code:
#RequestMapping(
consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
path = "api/api1",
method = RequestMethod.POST,
produces = MediaType.ALL_VALUE
)
public ResponseEntity<?> api1CallBack(#RequestBody String requestBody, HttpServletRequest request) throws IOException, GeneralSecurityException, URISyntaxException {
String response="{SOME_JSON}";
URI callbackURL = new URI("http://otherAPIEnv/api2");
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setLocation(callbackURL);
return new ResponseEntity<String>(response,httpHeaders, HttpStatus.OK);
}
I tried the above code, but when I hit the api1 through my curl I get the response on the same machine, but I want the response to be redirected to api2 at otherAPIEnv machine.
Could someone please suggest how to achieve this kind of request and response?
When you send a request to a URL it should respond to the same otherwise client will be in waiting for it until it times out.
So, the approach should be different in this scenario.
First, in your main rest API you have to send a response code to release the client.
Then, in the API method you have to call another method asynchronously which calls api2 and performs the desired operation.
Here is a simple example.
#Autowired
API2Caller api2Caller;
#RequestMapping(
consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
path = "api/api1",
method = RequestMethod.POST,
produces = MediaType.ALL_VALUE
)
#ResponseStatus(HttpStatus.ACCEPTED)
public void api1CallBack(#RequestBody String requestBody, HttpServletRequest request) throws IOException, GeneralSecurityException, URISyntaxException {
api2Caller.callApi2(requestBody);
}
and the APICaller should look like following
#Component
public class API2Caller {
#Async
public SomeResultPojo callApi2() {
// use RestTemplate to call the api2
return restTemplate.postForObject("http://otherAPIEnv/api2", request, SomeResultPojo.class);
}
}
But you can choose your most comfortable way to perform asynchronous operation.
Look like a job for redirect.
String redirectMe() {
return "redirect:http://otherAPIEnv/api2"
}
As for the curl. You have POST mapping of the method so be sure to try it with curl -X POST... or change it to GET.
This the more modular and more generic way to do such kind of things:
public #ResponseBody ClientResponse updateDocStatus(MyRequest myRequest) {
ClientResponse clientResponse = new ClientResponse(CTConstants.FAILURE);
try {
HttpHeaders headers = prepareHeaders();
ClientRequest request = prepareRequestData(myRequest);
logger.info("cpa request is " + new Gson().toJson(request));
HttpEntity<ClientRequest> entity = new HttpEntity<ClientRequest>(request, headers);
String uri = cpaBaseUrl + updateDocUrl ;
ClientResponse serviceResponse = Utilities.sendHTTPRequest(uri, entity);
clientResponse = serviceResponse;
if (serviceResponse != null) {
if (CTConstants.SUCCESS.equalsIgnoreCase(serviceResponse.getStatus())) {
clientResponse.setStatus(CTConstants.SUCCESS);
clientResponse.setMessage(" update success.");
}
}
} catch (Exception e) {
logger.error("exception occurred ", e);
clientResponse.setStatus(CTConstants.ERROR);
clientResponse.setMessage(e.getMessage());
}
return clientResponse;
}
public static ClientResponse sendHTTPRequest(String uri, HttpEntity<ClientRequest> entity) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new SimpleClientHttpRequestFactory());
SimpleClientHttpRequestFactory rf = (SimpleClientHttpRequestFactory) restTemplate.getRequestFactory();
rf.setReadTimeout(CTConstants.SERVICE_TIMEOUT);
rf.setConnectTimeout(CTConstants.SERVICE_TIMEOUT);
ParameterizedTypeReference<ClientResponse> ptr = new ParameterizedTypeReference<ClientResponse>() {
};
ResponseEntity<ClientResponse> postForObject = restTemplate.exchange(uri, HttpMethod.POST, entity, ptr);
return postForObject.getBody();
}
You need to use redirect and modify the return type of your method
public String api1CallBack(#RequestBody String requestBody, HttpServletRequest request) throws IOException {
return "redirect:http://otherAPIEnv/api2";
}
I'm handling an exception in my project
This is my GET endpoint:
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<V6SubnetRec> get(#RequestBody V6SubnetRequest requestBody) throws QIPException {
Site site = getSite(requestBody.getOrganization());
V6SubnetRec wsSubnet = (V6SubnetRec) requestBody.getV6Subnet();
V6SubnetRec v6SubnetRec = null;
try {
v6SubnetRec = getQipService1().getV6Subnets(wsSubnet, site);
} catch (Exception e) {
log.error(Keys.QIP_CALLOUT_ERROR, e);
throw new RestException(e);
}
return new ResponseEntity<V6SubnetRec>(v6SubnetRec, HttpStatus.OK);
}
#ExceptionHandler(RestException.class)
public ResponseEntity rulesForRestException(RestException restEx){
return new ResponseEntity(restEx.getResponse().getContent(), restEx.getResponse().getStatus());
}
RestException.java
#XmlRootElement(name = "RestException")
#XmlAccessorType(XmlAccessType.FIELD)
public class RestException extends RuntimeException{
#XmlElement
RestResponse response;
public RestException(Exception e){
//...
}
}
When I request with URL http://localhost/api/v1/v6subnet.json (return with JSON format), it will return HTTP status code 404 and include the content. It's OK.
But when I request with URL http://localhost/api/v1/v6subnet.xml (return with XML format) with the same request, it return HTTP status code 500 like a normal exception which is not handled as JSON format
I want to have results like when I request to JSON format.
Thanks.
I've fixed my issue. It's only change from
restEx.getResponse().getContent()
into
restEx.getResponse().getContent().toString()
I'm trying to have a #RestController which takes a #PathVariable return a specific object in JSON format, along with proper status code. So far the way the code is, it will return the object in JSON format because it is using Spring 4 built in Jackson library by default.
However I do not know how to make it so it will give a message to the user saying we want an api variable, then JSON data, then Error code (Or success code depending if all went well). Example output would be:
Please enter api value as parameter (NOTE this can be in JSON as well if needed)
{"id": 2, "api": "3000105000" ... } (NOTE this will be the JSON response object)
Status Code 400 (OR proper status code)
The url with parameter look like this
http://localhost:8080/gotech/api/v1/api/3000105000
The code I have so far:
#RestController
#RequestMapping(value = "/api/v1")
public class ClientFetchWellDataController {
#Autowired
private OngardWellService ongardWellService;
#RequestMapping(value = "/wells/{apiValue}", method = RequestMethod.GET)
#ResponseBody
public OngardWell fetchWellData(#PathVariable String apiValue){
try{
OngardWell ongardWell = new OngardWell();
ongardWell = ongardWellService.fetchOneByApi(apiValue);
return ongardWell;
}catch(Exception ex){
String errorMessage;
errorMessage = ex + " <== error";
return null;
}
}
}
A #RestController is not appropriate for this. If you need to return different types of responses, use a ResponseEntity<?> where you can explicitly set the status code.
The body of the ResponseEntity will be handled the same way as the return value of any #ResponseBody annotated method.
#RequestMapping(value = "/wells/{apiValue}", method = RequestMethod.GET)
public ResponseEntity<?> fetchWellData(#PathVariable String apiValue){
try{
OngardWell ongardWell = new OngardWell();
ongardWell = ongardWellService.fetchOneByApi(apiValue);
return new ResponseEntity<>(ongardWell, HttpStatus.OK);
}catch(Exception ex){
String errorMessage;
errorMessage = ex + " <== error";
return new ResponseEntity<>(errorMessage, HttpStatus.BAD_REQUEST);
}
}
Note that you don't need #ResponseBody on a #RequestMapping method within a #RestController annotated class.
The idiomatic way would be to use an exception handler instead of catching the exception in your regular request handling method. The type of exception determines the response code. (403 for security error, 500 for unexpected platform exceptions, whatever you like)
#ExceptionHandler(MyApplicationException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleAppException(MyApplicationException ex) {
return ex.getMessage();
}
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleAppException(Exception ex) {
return ex.getMessage();
}
I've an heartbeat API implemeted using Spring REST service:
#RequestMapping(value = "heartbeat", method = RequestMethod.GET, consumes="application/json")
public ResponseEntity<String> getHeartBeat() throws Exception {
String curr_time = myService.getCurrentTime();
return Util.getResponse(curr_time, HttpStatus.OK);
}
And MyService.java has below method:
public String getCurrentTime() throws Exception {
String currentDateTime = null;
MyJson json = new MyJson();
ObjectMapper mapper = new ObjectMapper().configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
try {
Date currDate = new Date(System.currentTimeMillis());
currentDateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").format(currDate);
json.setTime(currentDateTime);
ObjectWriter writer = mapper.writerWithView(Views.HeartBeatApi.class);
return writer.writeValueAsString(json);
} catch (Exception e) {
throw new Exception("Excpetion", HttpStatus.BAD_REQUEST);
}
}
It works as expected but have 2 issues:
When I invoke this API, Content-Type header is mandatory & I want to know how to make this header optional.
How to add "Accept" header so that it can support other format such as Google Protobuf?
Thanks!
If you don't want to require Content-Type exist and be "application/json", you can just omit the consumes section entirely.
"Accept" is available via the "produces" value, as opposed to "consumes." So if you wanted to support Google Protobuf OR application/json, you could do this:
#Controller
#RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json")
#ResponseBody
public ResponseEntity<String> getHeartBeat() throws Exception {
String curr_time = myService.getCurrentTime();
return Util.getResponse(curr_time, HttpStatus.OK);
}