I'm a beginner and i'm writing unittests and I've stumbled across something I can't find a solution for that fits my needs.
I want to write some Junit Test for that exceptions.
There is my class with my Method
#ControllerAdvice
#RestController
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(MethodArgumentTypeMismatchException.class)
public final ResponseEntity<AccessError> numberFormatExceptionNotFoundException(
MethodArgumentTypeMismatchException ex, NumberFormatException exe, WebRequest request) {
AccessError errorDetails = new AccessError();
errorDetails.code("400");
errorDetails.addErrorsItem(new Error("400",ex.getMessage()));
errorDetails.setCode("400");
errorDetails.setTimestamp(new Date().toInstant().atOffset(ZoneOffset.UTC));
errorDetails.setMessage(HttpStatus.BAD_REQUEST.getReasonPhrase());
errorDetails.setPath(((ServletWebRequest) request).getRequest().getRequestURI());
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
#Override
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
AccessError errorDetails = new AccessError();
errorDetails.code("400");
errorDetails.addErrorsItem(new Error("400","Media Type Not Supported Exception"));
errorDetails.setCode("400");
errorDetails.setTimestamp(new Date().toInstant().atOffset(ZoneOffset.UTC));
errorDetails.setMessage(HttpStatus.BAD_REQUEST.getReasonPhrase());
errorDetails.setPath(((ServletWebRequest) request).getRequest().getRequestURI());
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
And there is my testClass :
public class CustomizedResponseEntityExceptionHandlerTest {
#Mock
ResponseEntity<AccessError> responseEntity;
WebRequest webRequest;
#InjectMocks
private CustomizedResponseEntityExceptionHandler custom = new CustomizedResponseEntityExceptionHandler();
#Test
public void numberFormatExceptionNotFoundExceptionTest() {
WebRequest webRequest;
String msg = "toto";
AccessError errors = new AccessError();
errors.setPath("app");
errors.getPath();
errors.setTimestamp(new Date().toInstant().atOffset(ZoneOffset.UTC));
errors.timestamp(new Date().toInstant().atOffset(ZoneOffset.UTC));
ApiException apiException = new ApiException(errors, msg);
ResponseEntity<AccessError> responseApi = custom.handleUserNotFoundException(apiException, webRequest.getHeaderNames());
assertThatExceptionOfType(ApiException.class);
}
My Question is : How i can do a JUnit Test for that cases, which have webRequest and some exceptions ?
I've tried a lot of thing but i think i don't have the right thinking method.
Thanks !!
I found the solution
private CustomizedResponseEntityExceptionHandler test = new CustomizedResponseEntityExceptionHandler();
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
#Test
public void numberFormatExceptionNotFoundExceptionTest() {
MethodArgumentTypeMismatchException expt = null ;
NumberFormatException exe = null;
servletRequest.setServerName("www.example.com");
servletRequest.setRequestURI("/v1/someuri");
servletRequest.addParameter("brand1", "value1");
servletRequest.addParameter("brand2", "value2");
WebRequest webRequest = new ServletWebRequest(servletRequest);
ResponseEntity<AccessError> result = test.numberFormatExceptionNotFoundException(expt,exe, webRequest);
assertNotNull(result);
}
You can use MockMvc to test your API for success/failure or any other custom response such as 404 Not found
A Sample snippet for example would look like this
public class MyTestClass{
#Autowired
private MockMvc mvc;
#Test
public void testMethod() {
MvcResult result = mvc.perform(post("/yourEndPoint")
.contentType("application/json") //Optional depending on your API Design
.content(content)) //Optional depending on your API Design
.andExpect(status().isOk()) //isOk , isBadRequest() and so on
.andReturn();
}
} //End of class
Refer this article which explains in a simple manner
https://howtodoinjava.com/spring-boot2/testing/spring-boot-mockmvc-example/
Related
I wrote a spring controller with following methods to deal with a callback http request,
#PostMapping ("/test")
public void notifyTranscodeResult(String paramStr){
...
}
#PostMapping ("/test2")
public void notifyTranscodeResult(#RequestBody ParamClass param){
...
}
but I get errors: Resolved exception caused by handler execution: org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported
I can't change the callback http request because they are from other third-party services, how can I change my controller to correctly get the request params?
You need to define consumes attribute.
#PostMapping (path = "/test2", consumes = {MediaType.APPLICATION_OCTET_STREAM_VALUE})
Here is the implementation with Unit Test Case.
#PostMapping(value = "/upload",
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public String demo(HttpServletRequest httpServletRequest) {
ServletInputStream inputStream;
try {
inputStream = httpServletRequest.getInputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
final List<String> list = new BufferedReader(new InputStreamReader(inputStream))
.lines().toList();
System.out.println(list);
return "Hello World";
}
Test Case
#Autowired
private MockMvc mockMvc;
#Test
public void shouldTestBinaryFileUpload() throws Exception {
mockMvc
.perform(MockMvcRequestBuilders
.post("/api/user/upload")
.content("Hello".getBytes())
.contentType(MediaType.APPLICATION_OCTET_STREAM))
.andExpect(MockMvcResultMatchers
.status()
.isOk())
.andExpect(MockMvcResultMatchers
.content()
.bytes("Hello World".getBytes()));
}
first of all I try to write unit-test for remote service to get weather and using restTemplate then implement ClientHttpRequestInterceptor to get remote request && remote response for logging purpose now i try to mock restTemplate to write unit-test and get the following error
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(anyObject(), eq("String by matcher"));
For more info see javadoc for Matchers class.
and here you here code i wrote for unit-test
#RunWith(MockitoJUnitRunner.class)
public class WeatherRemoteServiceTest {
#Mock CustomResttemplate restTemplate;
#Mock WeatherRemoteUtilies weatherUtilies;
#InjectMocks WeatherRemoteService weatherRemote;
#Test
public void testRetrieveWeather() {
ResponseEntity<String> MockResponse= new ResponseEntity<String>(HttpStatus.OK);
Mockito.when(weatherUtilies.buildRequestParams(Mockito.anyString()))
.thenReturn(Mockito.any(MultiValueMap.class));
ResponseEntity<String> responseEntity = new ResponseEntity<String>("sampleBodyString", HttpStatus.OK);
Mockito.when(restTemplate.buildRestTemplate().exchange(
Matchers.anyString(),
Matchers.any(HttpMethod.class),
Matchers.<HttpEntity<?>> any(),
Matchers.<Class<String>> any()
)
).thenReturn(responseEntity);
assertEquals(weatherRemote.retrieveWeather("ciaro").getStatusCode(), HttpStatus.OK);
}
}
and here is code for business logic itself
#Service
public class WeatherRemoteService {
private final Logger logger= LoggerFactory.getLogger(this.getClass());
#Value("${openweather.url}")
private String url;
#Autowired
private WeatherRemoteUtilies weatherUtilies;
#Autowired
private CustomResttemplate customRestTemplate;
public ResponseEntity<?> retrieveWeather(String city) {
logger.info(Constants.CLASS_NAME+this.getClass().getName()+Constants.METHOD_NAME+new Object() {}.getClass().getEnclosingMethod().getName());
logger.debug(Constants.METHOD_ARGUMENTS+city);
RestTemplate restRequest= customRestTemplate.buildRestTemplate();
HttpHeaders headers= new HttpHeaders();
headers.set("Accept",MediaType.APPLICATION_JSON_UTF8_VALUE);
UriComponentsBuilder uri= UriComponentsBuilder.fromUriString(url).
path("/data/2.5/weather")
.queryParams(weatherUtilies.buildRequestParams(city));
HttpEntity<String>entity= new HttpEntity<>(headers);
ResponseEntity<String>WeatherResponse=restRequest.exchange(uri.toUriString(), HttpMethod.GET, entity, String.class);
logger.info(Constants.END_METHOD);
return WeatherResponse;
}
}
code for RestTemplateInterceptor
public class RestTemplateInterceptor implements ClientHttpRequestInterceptor {
private final Logger logger =LoggerFactory.getLogger(this.getClass());
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
logRequest(request, body);
ClientHttpResponse httpResponse= execution.execute(request, body);
logResponse(httpResponse);
return httpResponse;
}
private void logRequest(HttpRequest request, byte[] body) throws IOException {
logger.info(Constants.START_REMOTE_REQUEST);
logger.info("URI :{}", request.getURI().toString());
logger.info("Method :{}", request.getMethod().toString());
logger.info("Headers :{}", request.getHeaders().toString());
logger.info("Request body :{}", new String(body,"UTF-8").toString());
logger.info(Constants.END_REMOTE_REQUEST);
}
private void logResponse(ClientHttpResponse response) throws IOException {
logger.info(Constants.START_REMOTE_RESPONSE);
logger.info("Status code :{}", response.getStatusCode().toString());
logger.info("Status text :{}", response.getStatusText().toString());
logger.info("Headers :{}", response.getHeaders().toString());
logger.info("Response body :{}", StreamUtils.copyToString(response.getBody(), Charset.defaultCharset()));
logger.info(Constants.END_REMOTE_RESPONSE);
}
}
then setInterceptor on RestTemplate factory using the following code
public class CustomResttemplate {
public RestTemplate buildRestTemplate() {
SimpleClientHttpRequestFactory simpleFactory= new SimpleClientHttpRequestFactory();
simpleFactory.setOutputStreaming(false);
ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(simpleFactory);
RestTemplate restTemplate= new RestTemplate(factory);
restTemplate.setInterceptors(Collections.singletonList(new RestTemplateInterceptor()));
return restTemplate;
}
}
Your problem is where you write
.thenReturn(Mockito.any(MultiValueMap.class))
You actually have to tell Mockito what to return. You can't use a matcher here. Matchers are for verifying, and for setting up what conditions to stub. You can't use them to tell Mockito what to return from a stubbed call.
Create a specific MultiValueMap to pass to thenReturn.
I am trying to customize my body error message.
My springboot version is 2.1.5.RELEASE
I want this:
{
"This should be application specific"
}
but I'm receiving this:
{
"timestamp": "2019-05-24T15:47:10.872+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Not Found (404)",
"path": "/github/gifojhuinh4w5"
}
My exception class is:
#ControllerAdvice
public class AppExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
protected ResponseEntity<Object> handleConflict(Exception ex, WebRequest request) {
String bodyOfResponse = "This should be application specific";
return handleExceptionInternal(ex, bodyOfResponse,
new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request);
}
}
My class where exception is captured
#Controller
#EnableAutoConfiguration
public class GitHub {
#RequestMapping(value ="/github/{usuario}", produces = "application/json; charset=UTF-8")
#ResponseBody
public ResponseEntity<Object> quantidadeRepositorios(#PathVariable(value = "usuario")String usuario) throws IOException {
HashMap<String, Integer> map = new HashMap<>();
RepositoryService service = new RepositoryService();
GitHubClient client = new GitHubClient();
Gson gson = new Gson();
client.setOAuth2Token("key");
map.put("Total",service.getRepositories(usuario).size()); // exception captured here
return new ResponseEntity<>(gson.toJson(map), HttpStatus.OK);
}
}
When exception is caught by ExceptionHandler, build a response entity and return it as below.
Create a ErrorResponseDTO object and set message to it.
public class ErrorResponseDTO {
private String errorMessage;
}
In exception handler, return that dto object.
#ExceptionHandler(Exception.class)
protected ResponseEntity<Object> handleConflict(Exception ex, WebRequest request) {
ErrorResponseDTO errorDTO = new ErrorResponseDTO();
errorDTO.setErrorMessage("This should be application specific");
return new ResponseEntity<>(errorDTO, HttpStatus.INTERNAL_SERVER_ERROR);
}
This will give you the payload, you are looking for.
{
"This should be application specific"
}
I have a demo filter to log request/response body and verify the request signature, if signature is wrong the filter would be redirect to my error controller. And I have some test case to check this filter:
post the correct signature by using RestTemplate
post the wrong signature by using RestTemplate
post the correct signature by using MockMvc
post the wrong signature by using MockMvc
when I run that, case 1,2,3 were correct. but the case 4 has some problem.
I found that case 4 has run this code, but it did not redirect to my error controller.
RequestDispatcher requestDispatcher = getServletContext().getRequestDispatcher("/error/signError");
requestDispatcher.forward(requestWrapper, responseWrapper);
Whether I used the default MockMvc by #Autowired or used the MockMvcBuilders to create, this code did not work.
So, I confuse how much different about the MockMvc and RestTemplate, and is RequestDispatcher possible to work by using MockMvc?
The filter code:
#Component
public class ApiFilter extends OncePerRequestFilter {
private static final Logger LOG = LoggerFactory.getLogger(ApiFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String requestId = UUID.randomUUID().toString();
String requestBody = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
LOG.info("request id [{}] requset body [{}]", requestId, requestBody);
ApiRequestWrapper requestWrapper = null;
ApiResponseWrapper responseWrapper = null;
try {
requestWrapper = new ApiRequestWrapper(requestId, request, requestBody);
responseWrapper = new ApiResponseWrapper(requestId, response);
System.out.println(request.getRequestURI());
System.out.println(request.getRequestURL());
System.out.println(request.getPathInfo());
System.out.println(getServletContext());
if (StringUtils.equalsIgnoreCase(request.getHeader(ApiConstant.HEAD_SIGN), DigestUtils.md5Hex(requestBody + ApiConstant.API_TOKEN))) {
filterChain.doFilter(requestWrapper, responseWrapper);
} else {
// redirect to error response
RequestDispatcher requestDispatcher = getServletContext().getRequestDispatcher("/error/signError");
requestDispatcher.forward(requestWrapper, responseWrapper);
}
} finally {
LOG.info("request id [{}] response body [{}]", requestId, responseWrapper);
}
}
}
and my test case like this.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class LoginContorllerTest {
#Autowired ObjectMapper objectMapper;
//#Autowired
MockMvc mockMvc;
#Autowired TestRestTemplate restTemplate;
#Autowired WebApplicationContext webApplicationContext;
#Autowired ApiFilter apiFilter;
#Before
public void init() throws Exception {
MockFilterConfig filterConfig = new MockFilterConfig(webApplicationContext.getServletContext(), "apiFilter");
apiFilter.init(filterConfig);
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).addFilter(apiFilter, "/api/*").build();
}
#Test
public void mockTest() throws Exception {
System.out.println("mockTest");
LoginParam param = new LoginParam();
param.setUsername("test");
param.setPassword("123456");
String requestBody = objectMapper.writeValueAsString(param);
String sign = DigestUtils.md5Hex(requestBody + ApiConstant.API_TOKEN);
String contentAsString = mockMvc
.perform(post("/api/login").header(ApiConstant.HEAD_SIGN, sign).contentType(MediaType.APPLICATION_JSON_UTF8).content(requestBody))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
// please forgive me using this way in the demo
System.out.println(contentAsString);
System.out.println();
}
#Test
public void mockSignErrorTest() throws Exception {
System.out.println("mockSignErrorTest");
LoginParam param = new LoginParam();
param.setUsername("test");
param.setPassword("123456");
String requestBody = objectMapper.writeValueAsString(param);
String contentAsString = mockMvc
.perform(post("/api/login").header(ApiConstant.HEAD_SIGN, "12254548858").contentType(MediaType.APPLICATION_JSON_UTF8).content(requestBody))
.andReturn()
.getResponse()
.getContentAsString();
System.out.println(contentAsString);
System.out.println();
}
#Test
public void restTest() throws Exception {
System.out.println("restTest");
LoginParam param = new LoginParam();
param.setUsername("test");
param.setPassword("123456");
String requestBody = objectMapper.writeValueAsString(param);
String sign = DigestUtils.md5Hex(requestBody + ApiConstant.API_TOKEN);
HttpHeaders headers = new HttpHeaders();
headers.add(ApiConstant.HEAD_SIGN, sign);
HttpEntity<LoginParam> httpEntity = new HttpEntity<LoginParam>(param, headers);
ResponseEntity<String> result = this.restTemplate.exchange("/api/login", HttpMethod.POST, httpEntity, String.class);
System.out.println(result.getBody());
System.out.println();
}
#Test
public void restSignErrorTest() throws Exception {
System.out.println("restSignErrorTest");
LoginParam param = new LoginParam();
param.setUsername("test");
param.setPassword("123456");
HttpHeaders headers = new HttpHeaders();
headers.add(ApiConstant.HEAD_SIGN, "123456789");
HttpEntity<LoginParam> httpEntity = new HttpEntity<LoginParam>(param, headers);
ResponseEntity<String> result = this.restTemplate.exchange("/api/login", HttpMethod.POST, httpEntity, String.class);
System.out.println(result.getBody());
System.out.println();
}
}
I want to perform a test on a controller method which throws an exception. The method is something like this:
#RequestMapping("/do")
public ResponseEntity doIt(#RequestBody Request request) throws Exception {
throw new NullPointerException();
}
When I try to test this method with following code part,
mockMvc.perform(post("/do")
.contentType(MediaType.APPLICATION_JSON)
.content(JSON.toJson(request)))
NestedServletException is thrown from Spring libraries. How can I test that NullPointerException is thrown instead of NestedServletException?
Our solution is rather a workaround: The exception is caught in advice and error body is returned as HTTP response. Here is how the mock works:
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setHandlerExceptionResolvers(withExceptionControllerAdvice())
.build();
private ExceptionHandlerExceptionResolver withExceptionControllerAdvice() {
final ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver() {
#Override
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(final HandlerMethod handlerMethod, final Exception exception) {
Method method = new ExceptionHandlerMethodResolver(TestAdvice.class).resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(new TestAdvice(), method);
}
return super.getExceptionHandlerMethod(handlerMethod, exception);
}
};
exceptionResolver.afterPropertiesSet();
return exceptionResolver;
}
Advice class:
#ControllerAdvice
public class TestAdvice {
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Object exceptionHandler(Exception e) {
return new HttpEntity<>(e.getMessage());
}
}
After than, following test method passes successfully:
#Test
public void testException
mockMvc.perform(post("/exception/path"))
.andExpect(status().is5xxServerError())
.andExpect(content().string("Exception body"));
}
Easier way is to inject #ExceptionHandler into your Spring Test Context or it throws exception right in MockMvc.perform() just before .andExpect().
#ContextConfiguration(classes = { My_ExceptionHandler_AreHere.class })
#AutoConfigureMockMvc
public class Test {
#Autowired
private MockMvc mvc;
#Test
public void test() {
RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/update")
.param("branchId", "13000")
.param("triggerId", "1");
MvcResult mvcResult = mvc.perform(requestBuilder)
.andExpect(MockMvcResultMatchers.status().is4xxClientError())
.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(__ -> Assert.assertThat(
__.getResolvedException(),
CoreMatchers.instanceOf(SecurityException.class)))
.andReturn();
}
That way MvcResult.getResolvedException() holds #Controller's exception!
https://stackoverflow.com/a/61016827/173149