How to generate exception in Spring MVC test from MockMultipartFile? - java

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

Related

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 create test for post request

I have a method which send post request in third party resource and returns CloseableHttpResponse result. I try (in first time) to implement junit test for my project.. I know how to test methods which returns simple objects but I have no idea - how to possible similar test method?
public CloseableHttpResponse POST(String path, Map<String, String> parameters) throws URISyntaxException, IOException {
List<NameValuePair> pairParameters = generateListOfNameValuePair(parameters);
URI uri = new URIBuilder()
.setScheme(SSL_SCHEME)
.setHost(HOST)
.setPath(path)
.build();
HttpRequestBase postMethod = new HttpPost(uri);
try {
((HttpPost) postMethod).setEntity(new UrlEncodedFormEntity(pairParameters, "UTF-8"));
} catch (UnsupportedEncodingException initE) {
initE.printStackTrace();
}
return session.getHttpClient().execute(postMethod, session.getHttpContext());
}
If you use spring boot, you can use Mockito unit test.
this is an example to show you how to implement mockMvc and unit test method.
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
public class StockPlanControllerTest {
#Autowired
public WebApplicationContext context;
public MockMvc mockMvc;
#Before
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
#Test
public void POST(String path, Map<String, String> parameters) throws URISyntaxException, IOException {
mockMvc.perform(post(path)
.contentType(MediaType.APPLICATION_JSON)
.param("paramkey", "paramvalue"))
.andExpect(status().isOk());
}
To learn more about Mockito unit test, this tutorial help you more.
mockito tutorial

Spring & Mockito : Actual Object getting called in Unit Test

This is the class which i am trying to test,
public class AuthErrorResponseWriter {
#Autowired
TransResponse svcResponse;
#Override
public void writeResponse(HttpServletResponse response) {
//Set the Http status
response.setStatus(HttpStatus.FORBIDDEN.value());
svcResponse.setMessage(Constants.AUTHENTICATION_ERROR);
svcResponse.setStatus(HttpStatus.FORBIDDEN.toString());
ObjectMapper mapper = new ObjectMapper();
//Write the response
try {
Writer writer = response.getWriter();
writer.write(mapper.writeValueAsString(svcResponse));
writer.flush();
writer.close();
} catch (IOException ioex) {
logger.error("Problem producing authentication error http response",ioex);
}
}
}
The unit test code i have written is below,
RunWith(SpringRunner.class)
#WebMvcTest({AuthErrorResponseWriter .class})
#ComponentScan("com.demo.service")
public class AuthErrorResponseWriterTest {
#Mock
HttpServletResponse responseMock;
#Before
public void setUp(){
MockitoAnnotations.initMocks(this);
}
#Test
public void testResponse(){
TransResponse mockResponse = new TransResponse();
mockResponse.setMessage(Constants.AUTHENTICATION_ERROR);
mockResponse.setStatus(HttpStatus.FORBIDDEN.toString());
AuthErrorResponseWriter authErrorWriter = new AuthErrorResponseWriter ();
PrintWriter writerMock = mock(PrintWriter.class);
try {
when(responseMock.getWriter()).thenReturn(writerMock);
} catch (IOException ioex) {
//assertTrue(false);
}
authErrorWriter.writeResponse(responseMock);
verify(responseMock).setStatus(HttpStatus.FORBIDDEN.value());
}
}
When i execute this Junit, am getting a null pointer exception for
svcResponse.setMessage(Constants.AUTHENTICATION_ERROR);
svcResponse is null, even though i have mocked it.
Please can someone point to me why its not picking up the mock object and looking for the actual.
Also if my writing the Junit is a proper way?
You may want to use Mockito's runner instead of Spring (from what I see, you do not need Spring's context at all):
#RunWith(MockitoJUnitRunner.class)
public class SubscriptionServiceTest {
#InjectMocks
private AuthErrorResponseWriter authErrorResponseWriter;
#Mock
TransResponse svcResponse;
#Mock
HttpServletResponse responseMock;
....
authErrorWriter.writeResponse(responseMock);
svcResponse is null, even though i have mocked it.
No, you haven't mocked it. This is what you are doing in your code:
TransResponse mockResponse = new TransResponse();
mockResponse.setMessage(Constants.AUTHENTICATION_ERROR);
mockResponse.setStatus(HttpStatus.FORBIDDEN.toString());
What you should be doing is something like this:
#Mock
private TransResponse mockResponse;
You'll have to inject the mocks in the Target class like this:
#InjectMocks
private AuthErrorResponseWriter authErrorWriter;
And use, authErrorWriter instead of creating a new instance of the class in your test.
And then you can do something like this:
Mockito.doNothing().when(mockResponse).setMessage(Constants.AUTHENTICATION_ERROR);

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.

Testing thrown exception in controller

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

Categories

Resources