I'm testing a spring rest controller using spring MockMVC. I am able to execute mock http requests and verify the response. My setup autowires the WebApplicationContext:
@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration
public class MyTestClass {
@Autowired
private WebApplicationContext webAppContext;
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webAppContext)
.apply(springSecurity()).build();
}
To tell MockMVC what controllers to initialize and add to the context I have an internal config class:
@Configuration
@ComponentScan(
basePackages = {"com.acme"},
useDefaultFilters = false,
includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value={MyRestController.class, MyRestControllerDependency.class})
})
@EnableWebMvc
static class MyTestClassConfig implements WebMvcConfigurer {
@Bean
public MyClassRepository myClassRepository() {
return Mockito.mock(MyClassRepository.class);
}
And this works like a charm. MockMVC spins up a container with a context that loaded MyRestController and serves the urls mapped by MyRestController.
Now, some of MyRestControllers methods are annotated with: @PreAuthorize("hasAuthority('ADMINISTRATOR')")
so the next step is to test that.
Reading the Integration part of the Spring security testing guide
spring security and test-mockmvc
it seems all I should do is call the org.springframework.security.test.web.servlet.setup.springSecurity() in the MockMvcBuilders .apply()
function calls.
This does not work, I get:
java.lang.IllegalStateException: springSecurityFilterChain cannot be null. Ensure a Bean with the name springSecurityFilterChain implementing Filter is present or inject the Filter to be used.
I tried adding a bean definition to my internal config class like so:
Filter springSecurityFilterChain() {
return new FilterChainProxy();
}
While this does provide a springSecurityFilterChain, the instance is empty and when I run the test I get a NPE:
java.lang.NullPointerException
at org.springframework.security.web.FilterChainProxy.getFilters(FilterChainProxy.java:224)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
Which, is understandable, by instantiating the FilterChainProxy directly, it is indeed empty.
I suspect I need to configure my component scan to include springs' instances of the FilterChainProxy. If that is the issue, I don't know what classes I should include. I have tried including all the classes:
includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value={TenantAPI.class, TenantManager.class}),
@ComponentScan.Filter(type = FilterType.REGEX, pattern="^org\\.springframework\\.security.*")})
But this gives the same error: "Ensure a Bean with the name springSecurity.." as when I run the test without the additional Regex filter.
Does anyone know how I can get the springSecurityFilterChain when I am being picky with the internal classes I let MockMVC instantiate?
I assume you must have a
@Configuration
class somewhere that extendsWebSecurityConfigurerAdapter
? This is the configuration you need to set up Spring security.You need to include this class in your component scan in your test configuration.