How to prevent NestedServletException when testing Spring endpoints?
Solution 1
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.
Solution 2
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.
Solution 3
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.
Solution 4
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 videos on Youtube
Philipp Jahoda
Creator of the MPAndroidChart: https://github.com/PhilJay/MPAndroidChart iOS version: https://github.com/danielgindi/Charts I know some things about Android UI and Canvas and spent 10+ years developing with Java. More recently I am specializing in iOS development with Swift and backend development with Kotlin (Spring). Usually I am working with MongoDB as a database solution and Angular for frontend. For more information about me and current projects, please visit my website, LinkedIn profile or have a look at my GitHub account.
Updated on June 07, 2022Comments
-
Philipp Jahoda almost 2 years
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?
-
Michal Borek almost 6 yearsIn my case the problem was with two instances of
@Configuration
that extendWebSecurityConfigurerAdapter
. Having this, it's init method is invoked and there might be a case when you have two SecurityFilterChains andExceptionTranslationFilter
is not being called by mockMvc. This filter is responsible for translation between AccessDeniedException and desired response code.
-