I currently have two ControllerAdvice in my application, I'm supposed to merge them into one.
But I need to test them before and after the merge, test the exception and the object that the controller return me.
I'm trying to make a jUnit test with Mockito but it seems impossible to test the exceptions without any context, without a controller, etc ...
Does anyone know how can I proceed to achieve what I'm trying to do ?
I also try to throw manually an exception but obviously it wasn't catched by the ControllerAdvice.
So basically here is what i'm trying to do:
Manually throw an exception
This exception is handled by my ControllerAdvice
Check the returned object (code & message)
Here is a sample of code I have:
#Before
public void setup() {
...
mockMvc = MockMvcBuilders.standaloneSetup(getController())
.setControllerAdvice(new GlobalControllerExceptionHandler())
.setCustomArgumentResolvers(resolver, resolver_0, resolver_1)
.setHandlerExceptionResolvers(exceptionResolver).build();
}
#Controller
#RequestMapping("/tests")
public static class RestProcessingExceptionThrowingController {
#RequestMapping(value = "/exception", method = GET)
public #ResponseBody String find() {
throw new EntityNotFoundException();
}
}
#Test
public void testHandleException() throws Exception {
mockMvc.perform(get("/tests/exception"))
.andExpect(new ResultMatcher() {
#Override
public void match(MvcResult result) throws Exception {
result.getResponse().getContentAsString().contains("global_error_test");
}
})
.andExpect(status().isNotFound());
}
I have the good status code at the end but it doesn't use my ControllerAdvice (I try with the debugger)
You can just call handler method directly
#ControllerAdvice
MyAdvice{
#ExceptionHandeler(listOfExxcetpions)
public ResponseEntity someOfMyExceptionsHandler(Exception e){
.....
}
}
and in test
MuTest{
private MyAdvice advice=new MyAdvice();
#Test
public void oneOfTests(){
Exception e=new SomeSortOfExceptionToTest();
resp=advice.someOfMyExceptionsHandler(e)
assertThat(resp).....dostuff;
}
}
If you want to test how spring integrates with your handlers - if your annotations are correct, ordering serialization etc - well that will be an integration test and you have to boot up test context - then you can throw exceptions directly from controller methods.
Related
We Spring developers know that if one tries to delete an entity that has other associated entities, a DataIntegrityViolationException is thrown.
I wrote a delete method catching both EmptyResultDataAccessException and DataIntegrityViolationException exceptions, throwing custom service-level exceptions for each case:
#Service
public class CityService {
#Autowired
private CityRepository repository;
public void delete(Long id) {
try {
repository.deleteById(id); // returns 204
}
catch (EmptyResultDataAccessException e) {
throw new ResourceNotFoundException("Id not found " + id); // returns 404
}
catch (DataIntegrityViolationException e) {
throw new DatabaseException("Integrity violation"); // returns 400
}
}
}
I've set all up so the first scenario returns 204, the second scenario returns 404, and the third scenario returns 400. Everything is working fine when I test it on Postman.
However, when I try to write an integrated test using MockMvc, the DataIntegrityViolationException scenario doesn't work! (the other two scenarios work).
#SpringBootTest
#AutoConfigureMockMvc
#Transactional
public class CityControllerIT {
#Autowired
private MockMvc mockMvc;
(...)
#Test
public void deleteShouldReturnBadRequestEventWhenDependentId() throws Exception {
mockMvc.perform(delete("/cities/{id}", 1L))
.andExpect(status().isBadRequest());
}
}
It's returning 204 instead of 400! I have printed some messages inside the try block and I have found that it is really not throwing an exception. The try block executes entirely, as there was no integrity violation.
#Service
public class CityService {
#Autowired
private CityRepository repository;
public void delete(Long id) {
try {
System.out.println("START");
repository.deleteById(id);
System.out.println("FINISH");
}
(...)
I am missing something about MockMvc fundamentals? Why integrity violation is being ignored when executing that MockMvc test?
I've saved a minimum-H2-just-clone-and-run project on Github:
https://github.com/acenelio/mockmvc-dataintegrity
DataIntegrityViolationException does NOT work properly with #Transactional, even in a common service method like:
#Service
public class MyService {
#Autowired
private MyRepository repository;
#Transactional
public void delete(Long id) {
try {
repository.deleteById(id);
}
catch (DataIntegrityViolationException e) {
// do something
}
}
}
Similarly, if you want to automate test a DataIntegrityViolationException scenario, you should NOT annotate your test class with #Transactional.
So if your writing transactional integrated tests (which rollback database for each test), you may want to create another test class without #Transactional annotation to test your DataIntegrityViolationException scenario.
In my camel context I have a route defined as follows:
from("direct:getPets")
.routeId("getPets")
.to("http://localhost:4321/pets")
The route works well and makes a call to the server. I'd like to test this route and check the headers but I have issues.
This is a spring project so my test class looks as follows:
#RunWith(CamelSpringBootRunner.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.After_EACH_TEST_METHO)
#UseAdviceWith
public class TestRestEndpoint {
#Autowired
private CamelContext camelContext;
#Produce(uri = "direct:getPets")
private ProducerTemplate callServer;
#EndpointInject(uri = "mock:catchTestEndpoint")
#Test
public void should_return_json() throws Exception {
camelContext.getRouteDefinitions("getPets").adviceWith(camelContext, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
weaveAddLast().to("mock:catchTestEndpoint");
}
});
camelContext.start();
callServer.sendBody("");
mockEndpoint.expectedMessageCount(1);
mockEndpoint.assertIsSatisfied();
}
}
The exchange fails and returns a null pointer.
org.apache.camel.CamelExecutionException: Exception occurred during execution on the exchange: Exchange[ID-PVJ-DEV97-03-2-1600178584697-0-1]
Sounds like your appended Mock is null.
If the code in your question really is your code, this is probably because you have a typo in your endpoint URI.
Notice the extra t between "catch" and "Test" in mock:catchtTestEndpoint
I have written the following code and the mockmvc.perform does not catch exception instead returns an error stack. I used the debugger to confirm that the controller throws the correct error. I am new to SpringBoot and do not understand why the exception is not being handled by the test controller.The following is my test controller which makes three Api calls to external services.The exception is returned by the controller but Mockmvc.perform fails to assert it.
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = { Application.class, ApplicationTest.class })
#AutoConfigureMockMvc
#ContextConfiguration(initializers = {WireMockInitializer.class})
public class myControllerIntegrationTest {
#Autowired
private MockMvc mockMvc;
#Autowired private WireMockServer wireMockServer;
#Autowired
private myController myController;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(myController)
.build();
}
#Test
#DisplayName("Should Return Execution Error")
public void shouldReturnExecutionErrorOnService() throws Exception {
// Making Three Api calls the controller internally invokes them
configureStubA(HttpStatus.INTERNAL_SERVER_ERROR, args, "invalidResponse.json");
configureStubB(HttpStatus.INTERNAL_SERVER_ERROR, args, args2, args3,
"invalidResponse.json");
configureStubC(HttpStatus.INTERNAL_SERVER_ERROR, args, "invalidResponse.json");
mockMvc
.perform(
get("/something")
.param("a", a)
.param("b", b)
.param("c", c)
.param("d", d)
.param("e", e.toArray(new String[] {})))
.andDo(print())
.andExpect(status().is5xxServerError())
.andExpect(result -> assertTrue(result.getResolvedException() instanceof
IllegalStateException));
}}
The API itself will never return an exception.
Think of it as when you call an API, you'll always get a response, right?
The way Spring Controller handle an Exception is that it has a default exception handler, that will convert any exception threw from your Controller into a Response object, which will then be converted to json/xml and return to you.
One way to get what you are expecting is by declaring your own exception, annotate it with #ResponseStatus, and pass to it the http status code you want the exception to be mapped to.
For example (I'm using Kotlin in the following snippets) you can declare the mapping between the http status code 500 and your exception in this way:
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
class MyException(message: String) : RuntimeException(message)
Then in your test you will be able to assert on the internal error as you are doing in your snippet.
mockMvc.perform(get("/foo")).andExpect(status().isInternalServerError)
For more details take a look here and here
In my spring boot application, I have created a custom exception handler using #ControllerAdvice, and a custom exception ServerException, when I throw the custom exception, it does not get caught by my customExcpetionHandler, though I am able to check whether actually the excpetion is thrown and it is getting thrown as shown by logs.
Below is the code for my ServerException:
public class ServerException extends Exception {
/**
*
*/
private static final long serialVersionUID = <uid>;
public ServerException(String message) {
super(message);
}
}
Below is my GlobalCustomExceptionHandler class:
#ControllerAdvice
#EnableWebMvc
public class GlobalCustomExceptionHandler extends ResponseEntityExceptionHandler{
#ExceptionHandler(ServerException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ModelMap handleServerException(ServerException ex) {
ModelMap modelMap = new ModelMap();
modelMap.addAttribute("status", "ERROR_400_Bad_Request");
modelMap.addAttribute("error_message", ex.getMessage());
return modelMap;
}
}
I am throwing the exception in one of the restcontroller as follows:
throw new ServerException("invalid server configs");
But I can only see the exception getting printed in log file, and not getting it as response mentioned in handleServerException() method of GlobalCustomExceptionHandler class.
What could be the reason ?
I have just reproduced Your copy-pasted piece of code with simple REST endpoint, and it works as expected:
#RestController
public class SystemController {
#GetMapping(value = "/system")
public ResponseEntity<Object> getSystem() throws ServerException {
if (true)
throw new ServerException("Checking this out");
return new ResponseEntity<>(HttpStatus.OK);
}
}
Calling http://localhost:8080/system
Results with:
{"status":"ERROR_400_Bad_Request","error_message":"Checking this out"}
I need bigger picture to help You. Paste controller that is throwing that as well as main application config class.
I have a Spring Boot application and want to cover my REST controllers by integration test.
Here is my controller:
#RestController
#RequestMapping("/tools/port-scan")
public class PortScanController {
private final PortScanService service;
public PortScanController(final PortScanService portScanService) {
service = portScanService;
}
#GetMapping("")
public final PortScanInfo getInfo(
#RequestParam("address") final String address,
#RequestParam(name = "port") final int port)
throws InetAddressException, IOException {
return service.scanPort(address, port);
}
}
In one of test cases I want to test that endpoint throws an exception in some circumstances. Here is my test class:
#RunWith(SpringRunner.class)
#WebMvcTest(PortScanController.class)
public class PortScanControllerIT {
#Autowired
private MockMvc mvc;
private static final String PORT_SCAN_URL = "/tools/port-scan";
#Test
public void testLocalAddress() throws Exception {
mvc.perform(get(PORT_SCAN_URL).param("address", "192.168.1.100").param("port", "53")).andExpect(status().isInternalServerError());
}
}
What is the best way to do that? Current implementation doesn't handle InetAddressException which is thrown from PortScanController.getInfo() and when I start test, I receive and error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is com.handytools.webapi.exceptions.InetAddressException: Site local IP is not supported
It is not possible to specify expected exception in #Test annotation since original InetAddressException is wrapped with NestedServletException.
Spring Boot Test package comes with AssertJ that has very convenient way of verifying thrown exceptions.
To verify cause:
#Test
public void shouldThrowException() {
assertThatThrownBy(() -> methodThrowingException()).hasCause(InetAddressException .class);
}
There are also few more methods that you may be interested in. I suggest having a look in docs.
In order to test the wrapped exception (i.e., InetAddressException), you can create a JUnit Rule using ExpectedException class and then set the expectMessage() (received from NestedServletException's getMessage(), which contains the actual cause), you can refer the below code for the same:
#Rule
public ExpectedException inetAddressExceptionRule = ExpectedException.none();
#Test
public void testLocalAddress() {
//Set the message exactly as returned by NestedServletException
inetAddressExceptionRule.expectMessage("Request processing failed; nested exception is com.handytools.webapi.exceptions.InetAddressException: Site local IP is not supported");
//or you can check below for actual cause
inetAddressExceptionRule.expectCause(org.hamcrest.Matchers.any(InetAddressException.class))
//code for throwing InetAddressException here (wrapped by Spring's NestedServletException)
}
You can refer the ExpectedException API here:
http://junit.org/junit4/javadoc/4.12/org/junit/rules/ExpectedException.html
You could define an exception handler
#ExceptionHandler(InetAddressException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public Response handledInvalidAddressException(InetAddressException e)
{
log e
return getValidationErrorResponse(e);
}
and then in your test you could do
mvc.perform(get(PORT_SCAN_URL)
.param("address", "192.168.1.100")
.param("port", "53"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.response").exists())
.andExpect(jsonPath("$.response.code", is(400)))
.andExpect(jsonPath("$.response.errors[0].message", is("Site local IP is not supported")));
I had the same issue and i fix it with org.assertj.core.api.Assertions.assertThatExceptionOfType :
#Test
public void shouldThrowInetAddressException() {
assertThatExceptionOfType(InetAddressException.class)
.isThrownBy(() -> get(PORT_SCAN_URL).param("address", "192.168.1.100").param("port", "53"));
}
I hope it's help you !