Testing thrown exception in controller - java

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

Related

Junit Test for exception and WebRequest

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/

How can a spring controller deal with application/octet-stream request?

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()));
}

How to generate exception in Spring MVC test from MockMultipartFile?

I am trying to write some unit tests for a controller in Spring MVC, and part of the controller method has the follwing code:
try {
newProjectFile.setFileType(fileType);
newProjectFile.setContent(BlobProxy.generateProxy(file.getInputStream(), file.getSize()));
} catch (Exception e) {
throw new BadUpdateException(e.getMessage());
}
I've set up a MockMultipartFile in my unit test, and would like to test the exception case here so that I can get a bad request response.
I've tried setting up something like the following:
unit test:
MockMultipartFile file = new MockMultipartFile("file", "receipts.zip", "application/zip", "".getBytes());
[...]
when(file.getInputStream()).thenThrow(IOException.class);
[...]
and I get the following error:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);
If I can't use 'when' on a MockMultipartFile like I would any normal mock object, and Mockito doesn't allow you to mock static methods, how can I get an exception to be thrown here?
Edit:
as mentioned in the commments, the MockMultipartFile is not from Mockito, hence the error mentioned above.
The question really is how to throw an exception in the try/catch block, which is presumably either by throwing an IOException on file.getInputStream(), or an UnsupportedOperationException on BlobProxy.generateProxy(), so that my method throws the BadUpdateException.
So my colleague found a good way to get around this using an anonymous inner class:
#Override
public InputStream getInputStream() throws IOException {
throw new IOException();
}
};
This means that an exception is thrown in the try/catch block in the controller method when trying to get the InputStream from the MockMultipartFile, and the result is the BadUpdateException.
Here is the complete code for uploading an Excel file as multipart file. This is based on M Hall's previous response, so he/she should take credit for it.
This is the controller which will allow you to make the upload:
public class MyController {
#PostMapping({"/upload"})
public String upload(#RequestParam("excelFile") MultipartFile excelFile) {
try {
//This should throw an IOException
InputStream in = excelFile.getInputStream();
} catch (IOException e) {
//handle exception
}
return "redirect:/index";
}
}
This is how the test should look like:
#SpringBootTest(classes = {MyController.class})
public class MyControllerTest {
public static final String CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
public static final byte[] CONTENT = "xml content".getBytes();
//Create a custom mock multipart file. This file will throw an IOException, when the method getInputStream is called.
CustomMockMultipartFile excelFile = new CustomMockMultipartFile("excelFile", "MyExcelFile.xlsx", CONTENT_TYPE, CONTENT);
#Autowired
private WebApplicationContext wac;
#Autowired
MyController myController;
#Test
public void testUploadIoException() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.multipart("/upload").file(excelFile))
.andExpect(redirectedUrl("/index"))
.andReturn();
//Perform other assertions based on your business needs and test specifications
//Assert that the logic in the catch block is executed
}
//A private inner class, which extends the MockMultipartFile
private class CustomMockMultipartFile extends MockMultipartFile {
public CustomMockMultipartFile(String name, String originalFilename, String contentType, byte[] content) {
super(name, originalFilename, contentType, content);
}
//Method is overrided, so that it throws an IOException, when it's called
#Override
public InputStream getInputStream() throws IOException {
throw new IOException();
}
}
}

SpringBootTest with MockMvcBuilders stand alone setup is not loading my ControllerAdvice despite setting it

I am creating my controller and controller advice like this:
Test class:
#RunWith(SpringRunner.class)
#SpringBootTest
public class TestController {
private MockMvc mockMvc;
#Mock
private MyService myService;
#Autowired
#InjectMocks
private MyController myController;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
//Build the controller mock handler
mockMvc = MockMvcBuilders
.standaloneSetup(MyController.class)
.setControllerAdvice(new MyControllerAdvice())
//This also doesn't work
//.setHandlerExceptionResolvers(createExceptionResolver())
.build();
}
//This also did not work
private ExceptionHandlerExceptionResolver createExceptionResolver() {
ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver() {
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
Method method = new ExceptionHandlerMethodResolver(MyControllerAdvice.class).resolveMethod(exception);
return new ServletInvocableHandlerMethod(new MyControllerAdvice(), method);
}
};
exceptionResolver.afterPropertiesSet();
return exceptionResolver;
}
/**
* Tests passing bad input to see if our exception handler is called.
*/
#Test
public void testBadRequest()
{
//Make a request object that has a bad input (e.g. bad date string)
MyRequest request = new MyRequest();
//Set the request values
request.setDate( "a" );
try
{
myController.getSomething( request );
}
catch (Exception e)
{
//It reaches here without ever reaching my controller advice in debugging
e.printStackTrace();
}
}
}
Controller advice:
#EnableWebMvc
#ControllerAdvice
#Component
public class MyControllerAdvice {
#ExceptionHandler(value = Exception.class)
public ResponseEntity<String> handleException(HttpServletRequest request, Exception exception) throws Exception
{
//This is never called (I'm using a debugger and have a breakpoint here)
return new ResponseEntity<String>(
"test",
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
There are two issues in your example:
MockMvcBuilders#standaloneSetup() receives Controller objects as parameters, not the Class objects. So it should be:
mockMvc = MockMvcBuilders
.standaloneSetup(new MyController())
.setControllerAdvice(new MyControllerAdvice())
.build();
You are calling myController.getSomething( request ) directly, while you should use previously built mockMvc. Direct call is unadvised as it's not processed with TestDispatcherServlet. Here is a couple of examples for mockMvc requests:
GET
mockMvc.perform(get("/testSomething"))
.andExpect(status().is5xxServerError())
.andReturn();
POST
mockMvc.perform(post("/testSomething")
.contentType(MediaType.APPLICATION_JSON)
.content(json)) //it's JSON string
.andExpect(status().is5xxServerError())
.andReturn();

Testing REST endpoints with custom exception handling

I am working on a project with Spring microservices (modules) and I want to test my REST endpoint using MockMvc. My testing works fine for cases where the request is valid but it is not working when requesting a url that is invalid. By not working I mean my custom exception handler (#ControllerAdvice) does not get called, the exception gets thrown and the test fails.
My exception handler and testing class are implemented in different modules.
common-module (ExceptionHandler)
#ControllerAdvice
public class CoreExceptionHandler {
#ExceptionHandler(value = Exception.class)
public ResponseEntity<ErrorMessageDTO> handleException(Exception ex, HttpServletRequest request) {
// Getting servlet request URL
String uri = request.getRequestURI();
HttpStatus a;
ErrorMessageDTO errorMessage;
if (ex instanceof CoreException) {
CoreException e = (CoreException) ex;
...
errorMessage = new ErrorMessageDTO(e, uri);
} else {
errorMessage = new ErrorMessageDTO(ex, uri);
...
}
return new ResponseEntity<ErrorMessageDTO>(errorMessage, a);
}
}
country-module
This is where my REST endpoint and Testing class are implemented. The common module dependency is included in this module's pom.xml and the packages are scanned through the main class.
CountryApplication.java
#EnableCaching
#EnableDiscoveryClient
#EnableAspectJAutoProxy
#SpringBootApplication(scanBasePackages = {
"com.something1.something2.something3.common.exception",
"com.something1.something2.something3.common.util.logged",
"com.something1.something2.something3.country"
})
public class CountryApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(CountryApplication.class, args);
}
...
}
CountryService.java
This is a method in my Service class.
#GetMapping("/{id:\\d+}")
public CountryDTO getCountryById(#PathVariable("id") Integer id) throws CoreException {
Country countryEntity = this.countryRepository.findOne(id);
// requesting for id that does not exist
if (countryEntity == null) {
throw new CoreException(CoreError.ENTITY_NOT_FOUND);
}
return this.countryMapper.daoToDto(countryEntity);
}
CountryServiceTest.java
#SpringBootTest
#AutoConfigureMockMvc
#AutoConfigureTestDatabase
#RunWith(SpringRunner.class)
public class CountryServiceTest {
...
#Autowired
private MockMvc mockMvc;
#Test
public void getByIdTest() throws Exception {
// Get by id exists
mockMvc.perform(get("/2"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andDo(print());
// Get by id not exists. NOT WORKING
mockMvc.perform(get("/100000"))
.andExpect(status().isNotFound())
.andExpect(content().contentType(contentType));
}
}
As I described above, the problem is that at the second request of the test method, the CoreExceptionHandler does not get called and the test fails throwing a:
NestedServletException: Request processing failed; nested exception is com.something1.something2.something3.common.exception.CoreException.
The dependency for the common module is well configured (at least when I am deploying in non-test mode) since I am using it for other things too, plus the ExceptionHandler gets called when I am not testing.
Another strange thing is that when I am deploying my Test, Spring Boot's logs show that the CoreExceptionHandler gets detected. This is the line. Detected #ExceptionHandler methods in coreExceptionHandler
There are two problems as explained below:
(1) ControllerAdvice not being set for MockMvc object in your CountryServiceTest class, which can be done as shown below:
MockMvc mockMvc = standaloneSetup(yourController)
.setHandlerExceptionResolvers(new CoreExceptionHandler())
.build();
(2) Because CoreException is wrapper by NestedServletException by the Spring Container, you need to use exception.getCause() to check your exception as shown below:
#ControllerAdvice
public class CoreExceptionHandler {
#ExceptionHandler(value = Exception.class)
public ResponseEntity<ErrorMessageDTO> handleException(Exception ex,
HttpServletRequest request) {
// Getting servlet request URL
String uri = request.getRequestURI();
HttpStatus a;
ErrorMessageDTO errorMessage;
//check with exception cause
if (ex.getCause() instanceof CoreException) {
CoreException e = (CoreException) ex;
...
errorMessage = new ErrorMessageDTO(e, uri);
} else if (ex instanceof CoreException) {
//this block will be used only when direct CoreException triggered
CoreException e = (CoreException) ex;
...
errorMessage = new ErrorMessageDTO(e, uri);
} else {
errorMessage = new ErrorMessageDTO(ex, uri);
...
}
return new ResponseEntity<ErrorMessageDTO>(errorMessage, a);
}
}
Also, I suggest not to handle all exception types in a single generic method, which will be very hard to support/maintain, rather split your CoreExceptionHandler using multiple #ExceptionHandler methods/classes.

Categories

Resources