When I create a case where exception is thrown by the code by calling APIs, at that time, ExceptionHandler is invoked as expected. But when I try creating the same case through unit tests, at that time, ExceptionHandler is not invoked. My classes are as below:
Controller.java
#Post("/XXX")
public ResponseEntity<List<CategoryTopicBean>> listCategoryTopics(#Body CategoryIdsRequestBean categoryIdsRequestBean) {
if (categoryIdsRequestBean.getCategoryIds().size() > MAX_ALLOWED_CATEGORY_SELECTION) {
throw new CustomException(SystemConstants.ResponseCode.ERROR, SystemConstants.ResponseMessage.OVERFLOW_MAX_CATEGORIES);
}
...
CustomExceptionHandler.java:
#Produces
#Singleton
#Requires(classes = {CustomException.class, ExceptionHandler.class})
public class CustomExceptionHandler implements ExceptionHandler<CustomException, HttpResponse> {
#Override
public HttpResponse handle(HttpRequest request, CustomException exception) {
return HttpResponse.ok(new ResponseEntity<>(exception.responseCode, exception.getMessage()));
}
}
XXXShould.java
#Test
public void should_list_category_topics() {
CategoryIdsRequestBean categoryIdsBean = new CategoryIdsRequestBean();
IdBean idBean = new IdBean();
idBean.setId(ID_1);
categoryIdsBean.setCategoryIds(Arrays.asList(idBean));
ResponseEntity<List<CategoryTopicBean>> responseEntity = topicController.listCategoryTopics(categoryIdsBean);
assertThat(SystemConstants.ResponseCode.SUCCESS).isEqualTo(responseEntity.getResponseCode());
assertThat(1).isEqualTo(responseEntity.getData().size());
categoryIdsBean = new CategoryIdsRequestBean();
categoryIdsBean.setCategoryIds(Arrays.asList(idBean, idBean, idBean, idBean, idBean, idBean));
responseEntity = topicController.listCategoryTopics(categoryIdsBean);
}
Can anyone please look into this, and help me out?
The problem here is, you are testing the controller by directly invoking the controller method
topicController.listCategoryTopics(categoryIdsBean).
This is not a good approach to test controller functionality. What you should do is use MicronautTest. MicronautTest will start an embedded server. Now you can use an HTTP client to hit the endpoint and retrieve the result.
Your code needs to be changed to something around the lines as below.
#MicronautTest
class HelloWorldTest {
#Inject
#Client("/")
RxHttpClient client;
#Test
public void should_list_category_topics() {
// Test Data
CategoryIdsRequestBean categoryIdsBean = new CategoryIdsRequestBean();
IdBean idBean = new IdBean();
idBean.setId(ID_1);
categoryIdsBean.setCategoryIds(Arrays.asList(idBean));
HttpRequest<String> request = HttpRequest.POST("/XXX", categoryIdsBean);
ResponseEntity<List<CategoryTopicBean>> retrieve = client.toBlocking().retrieve(request, ResponseEntity.class);
categoryIdsBean = new CategoryIdsRequestBean();
categoryIdsBean.setCategoryIds(Arrays.asList(idBean, idBean, idBean, idBean, idBean, idBean));
responseEntity = topicController.listCategoryTopics(categoryIdsBean);
}
}
For the exception case scenario, as the exception handler returns ResponseEntity<String>, you would need to make a minor change in the above code.
ResponseEntity<String> retrieve = client.toBlocking()
.retrieve(request, ResponseEntity.class);
If your controller calls any other service, do not forget to mock the behavior.
Related
I am writing a test to test the POST method for failure case in the controller.
It returns a 415, I am expecting 500. I have mocked the response using mockito.
ControllerTest
#Test
#DisplayName("POST /customers - Failure")
void createProductShouldFail() throws Exception {
// Setup mocked service
when(customerService.save(any(Customer.class))).thenThrow(HttpServerErrorException.InternalServerError.class);
RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/customers").accept(MediaType.APPLICATION_JSON)
.content("{\"name\":\"John\"}");
MvcResult result=mockMvc.perform(requestBuilder).andReturn();
MockHttpServletResponse response = result.getResponse();
// Validate the response code and content type
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), response.getStatus());
}
Controller
#PostMapping(path = "/customers")
public ResponseEntity<Customer> saveCustomer(#RequestBody Customer customer){
try {
// Create the new product
Customer savedCustomer = customerService.save(customer);
// Build a created response
return ResponseEntity
.created(new URI("/customers/" + savedCustomer.getId()))
.body(savedCustomer);
} catch (URISyntaxException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
Error:
HTTP Method = POST
Request URI = /customers
Parameters = {}
Headers = [Accept:"application/json", Content-Length:"15"]
Body = {"name":"John"}
Session Attrs = {}
Handler:
Type = com.prabhakar.customer.controller.CustomerController
Method = com.prabhakar.customer.controller.CustomerController#saveCustomer(Customer)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.HttpMediaTypeNotSupportedException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 415
Error message = null
Headers = [Accept:"application/json, application/*+json"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
org.opentest4j.AssertionFailedError:
Expected :500
Actual :415
But 415-Unsupported Media Type client error response code.
I have used the same payload for this method,it works.
#Test
#DisplayName("POST /customers - Success")
void createProductShouldSucceed() throws Exception {
// Setup mocked service
Customer mockCustomer = new Customer(1L, "John");
when(customerService.save(any(Customer.class))).thenReturn(mockCustomer);
this.mockMvc.perform(post("/customers")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\":\"John\"}"))
// Validate the response code and content type
.andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
//Validate returned json fields
.andExpect(jsonPath("$.id").value(1L))
.andExpect(jsonPath("$.name").value("John"));
}
Update I have added
#RestController
#EnableWebMvc
this throws an error as mocked But the code breaks near mockmvc.perform.
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.web.client.HttpServerErrorException$InternalServerError
How can I verify if this is working.
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), response.getStatus());
There are two thing you must have in account to solve the problem:
First, Instead of use .accept(MediaType.APPLICATION_JSON) you must use .contentType(MediaType.APPLICATION_JSON).
Second, the other thing you must have in account is, if you are not handling the exception (using a controller advice or other way) you must do it, because when you execute the firts step you will receive the following error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.web.client.HttpServerErrorException$InternalServerError
The workaround that I took was use #ExceptionHandler in the CustomerController to test your code (this isn't the best place to do this, depending what you are doing. Instead use a #ControllerAdvice. You can find some examples here https://www.baeldung.com/exception-handling-for-rest-with-spring).
Below the complete code that are used to recreate your case.
Customer.class
public class Customer {
private Long id;
private String name;
public Customer(Long id, String name) {
this.id = id;
this.name = name;
}
// + getter and setter
}
CustomerController.class
#RestController
public class CustomerController {
private final CustomerService customerService;
public CustomerController(CustomerService customerService) {
this.customerService = customerService;
}
#PostMapping(path = "/customers")
public ResponseEntity<Customer> saveCustomer(#RequestBody Customer customer) {
try {
// Create the new product
Customer savedCustomer = customerService.save(customer);
// Build a created response
return ResponseEntity
.created(new URI("/customers/" + savedCustomer.getId()))
.body(savedCustomer);
} catch (URISyntaxException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
// Code used to avoid the error explained in the second step
#ExceptionHandler
public ResponseEntity<?> handlingInternalServerError(HttpServerErrorException.InternalServerError ex) {
// code to be executed when the exception is thrown (logs, ...)
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
CustomerService.class
#Service
public class CustomerService {
public Customer save(Customer customer) {
return customer;
}
}
CustomerControllerTest.class
#SpringBootTest
#AutoConfigureMockMvc
class CustomerControllerTest {
#MockBean
private CustomerService customerService;
#Autowired
private MockMvc mockMvc;
#Test
#DisplayName("POST /customers - Failure")
void saveCustomer() throws Exception {
Customer customerMock = new Customer(1L, "John");
// Setup mocked service
when(customerService.save(any(Customer.class))).thenThrow(HttpServerErrorException.InternalServerError.class);
RequestBuilder requestBuilder = post("/customers")
.content("{\"name\":\"John\"}")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
MockHttpServletResponse response = result.getResponse();
// Validate the response code and content type
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), response.getStatus());
}
}
NOTE: This test was executed using Java 8 and JUnit5
Other NOTE based on your comment:
Ok. #prabhakar-maity, my recommendation based in your case is to use a #ExceptionHandler or #ControllerAdvice instead of try...catch. For example, you have 6 methods in your controller or several controllers and want to handle the same exception (Internal Server Error) and return the same info, so you’ll have to implement a try..catch in each method, while using #ControllerAdive (multiple controllers) or #ExceptionHandler (one controller) you implement your logic in one place
Check this question for more info LINK
You can reference Spring MVC Test Framework - Unsupported Media Type
You may be missing #EnableWebMvc annotation in your controller.
EDIT - for Comment:
Instead of
RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/customers").accept(MediaType.APPLICATION_JSON)
.content("{\"name\":\"John\"}");
MockHttpServletResponse response = result.getResponse();
// Validate the response code and content type
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(),
response.getStatus());
Try:
mockMvc.perform(requestBuilder)
.andExpect(status().isInternalServerError());
I am new to JUNIT and using RestTemplate to call my service, I'm getting 200 response for the same. But, I can't test the class using JUnit. Tried different approaches and getting 400 and 404. I want to post the request body (json) and test the status. Please let me know if there is any issue.
/**
* Rest client implementation
**/
public class CreateEmailDelegate implements CDM {
#Autowired
private RestTemplate restTemplate;
private String url = "http://example.com/communications/emails";
public ResponseEntity<CDResponse> createEmail(CDMEmailRequest cDRequest) throws UnavailableServiceException, InvalidInputException {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("SR_API_Key", SR_API_KEY);
httpHeaders.set("consumerIdentification", CONSUMER_IDENTIFICATION);
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity< CDMEmailRequest > cDRequestEntity = new HttpEntity<>( cDRequest, httpHeaders);
ResponseEntity< CDResponse > cDResponse = null;
try {
cDResponse = restTemplate.postForEntity(url, cDRequestEntity, CDResponse.class);
} catch (Exception e) {
LOGGER.error(e.getMessage());
throw e;
}
return cDResponse;
}
}
My Test class which return 404 status instead of 200
#RunWith(SpringJUnit4ClassRunner.class)
public class CreateEmailCommunicationDelegateTest {
#Before
public void setup() {
httpHeaders = new HttpHeaders();
httpHeaders.set("SR_API_Key", SR_API_KEY);
httpHeaders.set("consumerIdentification", CONSUMER_IDENTIFICATION);
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.wac);
this.mockMvc = builder.build();
}
public void testResponse() throws Exception, HttpClientErrorException, JsonProcessingException {
String url = "http://example.com/CommunicationDeliveryManagement-Service-1.0.0/communications/emails";
CDMEmailRequest anObject = new CDMEmailRequest();
ResultMatcher ok = MockMvcResultMatchers.status().isOk();
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, false);
ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter();
String requestJson = ow.writeValueAsString(anObject);
System.out.println(requestJson);
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post(url).contentType(MediaType.APPLICATION_JSON_UTF8).content(requestJson);
this.mockMvc.perform(builder).andExpect(ok).andDo(MockMvcResultHandlers.print());
}
}
My Test class using TestRestTemplate instead MockMvc returns 400
#RunWith(SpringJUnit4ClassRunner.class)
public class CreateEmailCommunicationDelegateTest {
#Before
public void setup() {
httpHeaders = new HttpHeaders();
// rest headers as above
}
#Test
public void testResponse() throws Exception, HttpClientErrorException, JsonProcessingException {
String url = "http://example.com/CommunicationDeliveryManagement-Service-1.0.0/communications/emails";
String username = "";
String password = "";
HttpEntity<CDMEmailRequest>
cDEntity = new HttpEntity<>(httpHeaders);
restTemplate = new TestRestTemplate(username, password);
responseEntity =
restTemplate.exchange(url, HttpMethod.POST, cDEntity,
CDResponse.class);
assertNotNull(responseEntity);
assertEquals(HttpStatus.OK,
responseEntity.getStatusCode());
}
}
I think you're trying to implement an integration test instead of an unit test, there is quite difference. MockMvc should be used to implement unit tests and TestRestTemplate for integration tests. You can't neither use it for testing a Client implementation.
See Unit and Integration Tests in Spring Boot
If you are working with Spring Boot you could achieve your goal using another approach see this question Spring boot testing of a rest client using #RestClientTest.
I am trying to send a body in a post request in a springboot application using rest template. Here is the controller:(I removed #RequestBody because I used application/x-www-form-urlencoded header)
#RestController
#CrossOrigin
#RequestMapping("/api")
public class SentimentParserController {
#Autowired
private SentimentParserService sentimentParserService;
#RequestMapping(value = "/something", method = RequestMethod.POST, consumes="application/x-www-form-urlencoded")
public ResponseEntity<mcResponse>getTheSentiments( mcSentimentRequestDTO sentimentRequestDTO){
return sentimentParserService.getSentimentsMc(sentimentRequestDTO);
}
}
I want to send the sentimentRequestDTO object(lang, key, and text) as the body in a post request to get the mcResponse:
public mcResponse parseTheSentiments(String text, Languages lang, String key) throws Exception {
RestTemplate restTemplate = new RestTemplate();
String request = "http://localhost:8080";
mcSentimentRequestDTO mSentiments =new mcSentimentRequestDTO(key,"EN",text);
HttpHeaders headers = new HttpHeaders();
headers.add("content-type", "application/x-www-form-urlencoded");
MultiValueMap<String, String> map= new LinkedMultiValueMap<String, String>();
map.add("key", key);
map.add("txt", text);
map.add("lang", Languages.ENGLISH.toString());
HttpEntity<MultiValueMap<String, String>> request1 = new HttpEntity<MultiValueMap<String, String>>(map, headers);
mcResponse response = restTemplate.postForObject(request, request1 , mcResponse.class );
return response;
}
However, I am getting the following error: 404 null.
Can you please help me? Thanks in advance
and here is the service class:
public ResponseEntity<mcResponse> getSentimentsMc(mcSentimentRequestDTO sentimentRequestDTO){
ResponseEntity<mcResponse> dto = null;
try {
dto = sentimentConverter.getTheSentiments(mcsParser.parseTheSentiments(sentimentRequestDTO.getText(),
Languages.ENGLISH, sentimentRequestDTO.getKey()));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return dto;
}
Looks like variable request should be
String request = "http://localhost:8080/something";
Also if controller class has prefix, this prefix also should be in request.
I mean if your class looks like this
#RestController
#RequestMapping("/myApi")
public class CertificateController {
....
#RequestMapping(value = "/something", method = RequestMethod.POST)
public ResponseEntity<mcResponse>getTheSentiments( mcSentimentRequestDTO sentimentRequestDTO){
return sentimentParserService.getSentimentsMc(sentimentRequestDTO);
}
Then request should be
String request = "http://localhost:8080/myApi/something";
It sounds like the controller isn't getting included in the spring context. If you just have an app annotated with #SpringBootApplication, then make sure that your controller is in a package that is the same as or lower than your annotated application.
To check the controller is being picked up you can add the following logging options to your application.properties
logging.level.org.springframework.beans=debug
logging.level.org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping=trace
When your server starts up you should see something like the following in the log
1. To show the controller is in the spring-context
DefaultListableBeanFactory : Creating shared instance of singleton bean 'sentimentParserController'
2. To show the mapping for the /api/something url
RequestMappingHandlerMapping : Mapped 1 handler method(s) for class SentimentParserController: {public org.springframework.http.ResponseEntity SentimentParserController.getTheSentiments(mcSentimentRequestDTO)={[/api/something],methods=[POST]}}
If you see both of these, then what you say you're doing should work. Just make sure you are sending the request to /api/something and the server is running on port 8080.
I am working in AWS(Amazon web service) , mockito and java junit4 environment. In my class I am using one method which takes Request object as a parameter and depending on that object I am getting response. Following is my code,
private Response<String> getStringResponse(Request<?> request) {
try {
AmazonHttpClient client = new AmazonHttpClient(new ClientConfiguration());
ExecutionContext executionContext = new ExecutionContext(true);
HttpResponseHandler<AmazonClientException> handler = getErrorResponseHandler();
HttpResponseHandler<String> responseHandler = getHttpResponseHandler();
RequestExecutionBuilder requestExecutionBuilder = client.requestExecutionBuilder();
requestExecutionBuilder = requestExecutionBuilder.executionContext(executionContext);
requestExecutionBuilder = requestExecutionBuilder.request(request);
requestExecutionBuilder = requestExecutionBuilder.errorResponseHandler(handler);
Response<String> response = requestExecutionBuilder.execute(responseHandler);
return response;
} catch (Exception e) {
AppLogger.getLogger().error("Exception in :: classname :: getStringResponse() ::");
throw e;
}
}
What I want to do is, I want to mock this whole scenario means on whatever request, my method should give me the custom response object which I want, irrespective of what Request is coming. I am calling this method from my junit test. So is there anyway to do this?
Before you can test this method with JUnit and Mockito,
you should write a clean and testable codes.
Importantly, you have to remove all dependencies inside method and initialize them from outside. For example,
private Response<String> getStringResponse(Request<?> request,
AmazonHttpClient client,
ExecutionContext executionContext,
HttpResponseHandler<AmazonClientException> handler,
HttpResponseHandler<String> responseHandler) {
try {
RequestExecutionBuilder requestExecutionBuilder = client.requestExecutionBuilder()
.executionContext(executionContext)
.request(request)
.errorResponseHandler(handler);
Response<String> response = requestExecutionBuilder.execute(responseHandler);
return response;
} catch (Exception e) {
AppLogger.getLogger().error("Exception in :: classname :: getStringResponse() ::");
throw e;
}
}
Now you can test it by mocking these above dependencies.
#Mock
AmazonHttpClient client;
#Mock
ExecutionContext executionContext;
#Mock
HttpResponseHandler<AmazonClientException> handler;
#Mock
HttpResponseHandler<String> responseHandler;
// For request, you can create a custom one or use mock data
Request<?> request;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
// other setups
}
#Test
public void getStringResponseTest() {
// you can test now
yourCall.getStringResponse(request, client, executionContext, handler, responseHandler);
// verify whatever you want....
}
Good morning,
I'm trying to test some POST requests on my controllers.
I have no problems with GET request :
#Test
public void testGetAll() {
TestModel test = new TestModel();
test.done = true;
test.name = "Pierre";
test.save();
TestModel test2 = new TestModel();
test2.done = true;
test2.name = "Paul";
test2.save();
Result result = new controllers.ressources.TestRessource().get(null);
assertEquals(200, result.status());
assertEquals("text/plain", result.contentType());
assertEquals("utf-8", result.charset());
assertTrue(contentAsString(result).contains("Pierre"));
assertTrue(contentAsString(result).contains("Paul"));
}
But when I have to test a POST request, i can't give POST params to the controller.
here is the method I want to test :
public Result post() {
Map<String, String> params = RequestUtils.convertRequestForJsonDecode(request().queryString());
T model = Json.fromJson(Json.toJson(params), genericType);
model.save();
reponse.setData(model);
return ok(Json.prettyPrint(Json.toJson(reponse)));
}
I've try several solutions, but I can't find a proper one :
Try to use FakeRequest
Try to Mock the Http.Request object
So, what is the best way to write tests for my controllers ?
I'm using Play Framework 2.4.6 with Java.
Junit 4 and Mockito.
For tests of an POST action I use the RequestBuilder and the play.test.Helpers.route method.
For one with JSON data it could look like this (I use Jackson's ObjectMapper for marshaling):
public class MyTests {
protected Application application;
#Before
public void startApp() throws Exception {
ClassLoader classLoader = FakeApplication.class.getClassLoader();
application = new GuiceApplicationBuilder().in(classLoader)
.in(Mode.TEST).build();
Helpers.start(application);
}
#Test
public void myPostActionTest() throws Exception {
JsonNode jsonNode = (new ObjectMapper()).readTree("{ \"someName\": \"sameValue\" }");
RequestBuilder request = new RequestBuilder().method("POST")
.bodyJson(jsonNode)
.uri(controllers.routes.MyController.myAction().url());
Result result = route(request);
assertThat(result.status()).isEqualTo(OK);
}
#After
public void stopApp() throws Exception {
Helpers.stop(application);
}
}