6

I'm trying to follow this article [1] to mock security in my Spring MvcMock test.

The REST service I want to test looks like this:

@RequestMapping(value = "/something/{id}", method = RequestMethod.DELETE)
public ResponseEntity<Void> deleteXXX(@ActiveUser AppUser user, @PathVariable(value = "id") Long id) {
  ...
}

where @ActiveUser is a custom implementation/extension of @AuthenticationPrincipal and AppUser is the custom UserDetails implementation.

In the test I do this:

mockMvc.perform(delete("/something/{i}", "123").with(user(new AppUser(...))));

I also added some TestExecutionLIsteners:

@TestExecutionListeners(listeners = { ServletTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class, WithSecurityContextTestExecutionListener.class })

```

But it fails with:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.corp.AppUser]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.corp.AppUser.<init>()
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:973)
at org.springframework.web.servlet.FrameworkServlet.doDelete(FrameworkServlet.java:885)

which is OK, as AppUser does not have a default constructor, but the framework should actually not create the user, but use the one I passed into the test.

To solve this issue during runtime I had to add AuthenticationPrincipalArgumentResolver as a HandlerMethodArgumentResolver to the web config, but how do I do this in the test case?

Is there any working example for this?

[1] https://spring.io/blog/2014/05/23/preview-spring-security-test-web-security

edit:

The testclass and its configuration looks like this:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { MyControllerTest.DummyAppConfig.class,  MyControllerTest.DummySecurityConfiguration.class })
@TestExecutionListeners(listeners = { ServletTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class, WithSecurityContextTestExecutionListener.class })
@WebAppConfiguration
public class MyControllerTest {


@Test
public void doTest() throws Exception {
    mockMvc.perform(delete("/something/{i}", "123").with(user(new AppUser(...))));
}


@Configuration
public static class DummyAppConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new AuthenticationPrincipalArgumentResolver());
}

    @Bean
    public MyController aController() {
        return new MyController();
    }

}

@Configuration
@EnableWebSecurity
public static class DummySecurityConfiguration extends WebSecurityConfigurerAdapter {
}

```

And this is the full stacktrace:

```

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.corp.AppUser]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.corp.AppUser.<init>()
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:973)
at org.springframework.web.servlet.FrameworkServlet.doDelete(FrameworkServlet.java:885)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:694)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:62)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:770)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:170)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:137)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:145)
at com.corp.ControllerTest.doTest(RestCorporateAccountSearchProfilesControllerTest.java:231)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:72)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:81)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:216)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:82)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:60)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:67)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:162)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.corp.AppUser]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.corp.AppUser.<init>()
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:107)
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.createAttribute(ModelAttributeMethodProcessor.java:138)
at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.createAttribute(ServletModelAttributeMethodProcessor.java:81)
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:104)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:79)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:157)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:124)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:781)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:721)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:943)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:877)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
... 39 more
Caused by: java.lang.NoSuchMethodException: com.corp.AppUser.<init>()
at java.lang.Class.getConstructor0(Class.java:2800)
at java.lang.Class.getDeclaredConstructor(Class.java:2043)
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:104)
... 52 more

```

domi
  • 2,167
  • 1
  • 28
  • 45
  • What does the Security Configuration look like? Are you using @EnableWebMvcSecurity? Also please provide the complete stacktrace. – Rob Winch Sep 29 '14 at 16:41
  • I added the stacktrace and the configuration (which is embedded in the test case) – domi Sep 30 '14 at 09:37
  • The construct `@ActiveUser AppUser user` tries to create `user` with `SecurityContextHolder.getContext().getAuthentication()getPrincipal()`. Have you given as authentication an object implementing `Authentication` and returning an `AppUser` in its `getPrincipal()` method ? – Serge Ballesta Sep 30 '14 at 11:48
  • If that's the case, then the whole point of `...with(user(new AppUser(..)` is lost and does not make sense. – domi Sep 30 '14 at 15:13

2 Answers2

4

If you use the MockMvcBuilders.standaloneSetup() method, you can add the resolver to the standalone MockMvc as so:

MockMvcBuilders.standaloneSetup(controller).setCustomArgumentResolvers(new AuthenticationPrincipalArgumentResolver()).build()

I too, had trouble getting it to work with the MockMvcBuilders.webAppContextSetup() method, but standalone worked without concern.

Mike Cornell
  • 5,909
  • 4
  • 29
  • 38
  • This seems to solve the exception from which this post originated, but now I'm seeing that my custom UserDetails object is coming into my controller as null. Don't suppose you'd have any suggestions? – akokskis Oct 23 '15 at 03:00
  • http://stackoverflow.com/questions/33294769/spring-security-test-and-mockmvc-supply-null-custom-userdetails-parameter-to-res for more details. – akokskis Oct 23 '15 at 04:15
0

You need to use @EnableWebMvcSecurity on DummySecurityConfiguration in order to get the Spring Security / MVC integration. See http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/#mvc-enablewebmvcsecurity

Rob Winch
  • 21,440
  • 2
  • 59
  • 76
  • still the exact same error... btw. I'm using spring-security-test:4.0.0.M2 with spring-webmvc:4.1.0.RELEASE – domi Sep 30 '14 at 15:14
  • do you have any running example of this in any repository. e.g. a testcase within Spring? – domi Oct 10 '14 at 13:34