How to prevent NestedServletException when testing Spring endpoints? - spring

I am trying to test the security configuration of some of my endpoints which are secured with #PreAuthorize(#oauth2.hasScope('scope'). When accessing such an endpoint via Postman with a access token that does not have the required scope, the following is returned with HTTP status code 403 (forbidden):
{
"error": "insufficient_scope",
"error_description": "Insufficient scope for this resource",
"scope": "scope"
}
Which is the expected behaviour that I want.
When trying to test this configuration, Springs NestedServletException interferes with my test case before it can complete with my expected result.
This is a simplified version of the controller I want to test:
#RestController
#RequestMapping(value = "/api")
public class OauthTestingResource {
#PreAuthorize(#oauth2.hasScope('scope'))
#RequestMapping(value = "/scope", method = RequestMethod.GET)
public void endpoint() {
// ...
}
}
And this is the corresponding test case:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = MyApplication.class)
#WebAppConfiguration
public class AuthorizationTest {
#Autowired
protected WebApplicationContext webApplicationContext;
protected SecurityContext securityContext = Mockito.mock(SecurityContext.class);
#Before
public void setup() throws Exception {
this.mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
SecurityContextHolder.setContext(securityContext);
}
protected Authentication createMockAuth(Client client) {
final List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
final Authentication pwAuth = new UsernamePasswordAuthenticationToken("testuser", "testpw", authorities);
final TokenRequest request = new TokenRequest(new HashMap<>(), client.getClientId(), client.getScopes(), "password");
final OAuthClient oauthClient = new OAuthClient(client, GrantType.PASSWORD);
return new OAuth2Authentication(request.createOAuth2Request(oauthClient), pwAuth);
}
#Test
public void testAppScope() throws Exception {
final Client client = new Client("id1", "secret1");
client.setScope("scope");
Mockito.when(securityContext.getAuthentication()).thenReturn(createMockAuth(client));
// this test passes
mvc.perform(get("/api/scope")).andExpect(status().isOk());
client.setScope("other_scope");
Mockito.when(securityContext.getAuthentication()).thenReturn(createMockAuth(client));
// NestedServletException thrown here
mvc.perform(get("/api/scope")).andExpect(status().isForbidden());
}
}
The exception that is thrown is the following (which is expected):
org.springframework.web.util.NestedServletException: Request
processing failed; nested exception is
org.springframework.security.access.AccessDeniedException:
Insufficient scope for this resource
My question is how can I prevent this exception from interfering with my test case?

I did spring security test cases by following this link. Things worked fine except this issue of nesting original exception in NestedServletException.
I did not find any direct way to figure this out but AspectJ helped me in handling this in a cleaner way.
We can use the static assertThatThrownBy() method of the Assertions class. This method returns an AbstractThrowableAssert object that we can use to write assertions for the thrown exception.
The code that captures an exception thrown by the methodThatThrowsException() method looks as follows:
assertThatThrownBy(() -> methodThatThrowsException())
.isExactlyInstanceOf(DuplicateEmailException.class);
Thanks to this excellent blog where you can find additional details.
The way in which I handled this in my test case would be (by taking your test case codeline):
org.assertj.core.api.Assertions.assertThatThrownBy(() -> mvc.perform(get("/api/scope")).andExpect(status().isOk())).hasCause(new AccessDeniedException("Access is denied"));
That way your test case would be able to assert actual AccessDeniedException that is nested in NestedServletException.

I had a similar case and suppressed the NestedServletException by using
#Test(expected = NestedServletException.class) and then I was able to get hold of the MvcResult and do further asserts on it as in other tests like:
// then
MvcResult result = resultActions.andExpect(status().isServiceUnavailable()).andReturn();
String message = result.getResponse().getContentAsString();
assertThat(message).contains("ABC");
assertThat(result.getResolvedException().getClass()).isEqualTo(XYZ.class);
It seemed to work.

I fixed it by adding an #ExceptionHandler for that exception. Seems like if MockMvc throws an actual exception it means you don't "handle" this case which is not ideal.

If you are using a centralised class to do your error handling via AOP (using #ControllerAdvice annotation), then include this ControllerAdvice in your test setup.
#BeforeEach
public void setup() {
this.mockMvc = standaloneSetup(myController).setControllerAdvice(MyErrorHandlingAdvice.class).build();
}

Related

Testing a camel route that calls an http server

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

Why doesnt Springboot Mockmvc.perform handle exceptions on controller?

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

How to test exceptions handling in #ControllerAdvice

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.

Spring Security and MockMvc - Need to mock authentication or principal

I'm using Spring Security, and facing issue writing unit test case (using MockMvc) for a controller.
I have a method in my controller that goes something like this:
#GetMapping
public ResponseEntity<User> getUser(#AuthenticationPrincipal User activeUser){
String userEmail = activeUser.getEmail();
return userService.getUser(userEmail);
}
I get a 500 error with this.
Another variation for the controller I've tried is, and this is working on Postman/Curl :
#GetMapping
public ResponseEntity<User> getUser(OAuth2Authentication authentication){
String userEmail = (String) authentication.getUserAuthentication().getPrincipal();
return userService.getUser(userEmail);
}
My service looks like :
public ResponseEntity<User> getUser(String email) {
return userRepository.findByEmail(email)
.map(record -> ResponseEntity.ok().body(record))
.orElse(ResponseEntity.notFound().build());
}
In my unit test case for this controller method, I have:
#Test
#WithMockUser(username = "1", password = "pwd", roles = "USER")
public void controller_should_get_user() throws Exception {
when(userService.getUser("1")).thenReturn(new ResponseEntity(userMock, HttpStatus.OK));
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/user/")).andExpect(status().isOk());
}
I am getting the following error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
at com.timecloud.user.controller.UserControllerTest.controller_should_get_user(UserControllerTest.java:60)
How should I go about passing or mocking a user with the current authentication? Thanks.
#WithMockUser creates a UsernameAuthenticationToken, not an OAuth2Authentication.
At least three solutions here:
Inject an OAuth2Authentication mock or instance in the security context
change your method to public ResponseEntity<User> getUser(Authentication authentication), then using authentication.getName() inside
use some existing tooling to apply solution 1. for you, like in this libs I wrote
Sample usage with solution 1
#Test
public void test() throws Exception {
final var storedRequest = mock(OAuth2Request);
final var principal = mock(Principal.class);
when(principal.getName()).thenReturn("user");
final var userAuthentication = mock(Authentication.class);
when(userAuthentication.getAuthorities()).thenReturn(Set.of(new SimpleGrantedAuthority("ROLE_USER"));
when(userAuthentication.getPrincipal()).thenReturn(principal);
final var oauth2Authentication = new OAuth2Authentication(storedRequest, authentication);
SecurityContextHolder.getContext().setAuthentication(oauth2Authentication);
// use MockMvc to test a #Controller or unit-test any other secured #Component as usual
}
Sample usage with solution 3
#Test
#WithMockAuthentication(authType = OAuth2Authentication.class, name = "user", authorities = "ROLE_USER")
public void test() throws Exception {
// use MockMvc to test a #Controller or unit-test any other secured #Component as usual
}
NullPointerException is coming because your test is unable to find anything for OAuth2Authentication Object. There are two things you can do your test case:
Try Mocking OAuth2Authentication in some setUp method.
OR
If you are using Spring 4.0+, the best solution is to annotate the test method with #WithMockUser
#Test
#WithMockUser(username = "user1", password = "pwd", roles = "USER")
public void mytest1() throws Exception {
//Your test scenario
}

Spring Boot Testing: exception in REST controller

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 !

Resources