4

I have a very small REST application written using spring boot.

I want to write a unit test for the authentication, but even if i add @MockWithUser to a test, i receive a 401 error.

The importent files are

the security configuration

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth
            .inMemoryAuthentication()
            .withUser("user").password("password").roles("USER");
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/**")
            .hasRole("USER")
            .anyRequest()
            .permitAll()
            .and()
            .anonymous().disable()
            .exceptionHandling()
            .authenticationEntryPoint(new org.springframework.boot.autoconfigure.security.Http401AuthenticationEntryPoint("headerValue"));
}

The MainApp with a controller

@Controller
@RequestMapping("/test")
@ComponentScan
@SpringBootApplication
public class MainApp {


@RequestMapping(method= RequestMethod.GET)
public @ResponseBody String sample(){

    return "Test";
}

public static void main(String[] args) throws Exception {
    SpringApplication.run(MainApp.class, args);
}
}

And finally the test (which is not working)

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MainApp.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class AuthenticationTest {

@Autowired
private TestRestTemplate testRestTemplate;

@Autowired
private WebApplicationContext context;

@Autowired
private Filter springSecurityFilterChain;

private MockMvc mvc;

@LocalServerPort
private int port;

@Before
public void setup() {
    mvc = MockMvcBuilders
            .webAppContextSetup(context)
            .addFilters(springSecurityFilterChain)
            .build();
}

@Test
@WithMockUser(username="admin",roles={"USER","ADMIN"})
public void shouldReturn200WhenSendingRequestToControllerWithRoleUser() throws Exception {
    then(SecurityContextHolder.getContext().getAuthentication().isAuthenticated());
    mvc.perform(get("/test")).andExpect(status().isOk());

}

@Test
@WithMockUser(username="admin",roles={"USER","ADMIN"})
public void shouldAuthenticatedBeTrueWithRoleUser() throws Exception {
    then(SecurityContextHolder.getContext().getAuthentication().isAuthenticated());
}
}

As you can see, there are two test cases. The second is passed, the first is not (receiving 401, not 200 as response code, stacktrace below).

Can you tell me how an can test authentication properly?

java.lang.AssertionError: Status expected:<200> but was:<401> at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:54) at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:81) at org.springframework.test.web.servlet.result.StatusResultMatchers$10.match(StatusResultMatchers.java:664) at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:171) at graphEndpoint.dataConnection.controller.AuthenticationTest.shouldReturn200WhenSendingRequestToControllerWithRoleUser(AuthenticationTest.java:64) 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) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) 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:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:114) at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:57) at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:66) at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51) 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) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32) at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93) at com.sun.proxy.$Proxy3.processTestClass(Unknown Source) at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:109) 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) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35) at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:377) at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54) at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)

AsconX
  • 161
  • 1
  • 15

1 Answers1

5

Simply using addFilters is not going to be sufficient to build the security context. Here are a couple solutions:

In your setup method replace

.addFilters(springSecurityFilterChain)

with

.apply(springSecurity())

from

org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;

That alone should resolve the test failures. This still leaves you with a nasty looking class that can be cleaned up quite a lot with the use of WebMvcTest. Here is a more succinct version of your test, using WebMvcTest.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = MainApp.class)
public class AuthenticationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithMockUser(username = "admin", roles = {"USER", "ADMIN"})
    public void shouldReturn200WhenSendingRequestToControllerWithRoleUser() throws Exception {
        mockMvc.perform(get("/test")).andExpect(status().isOk());
    }
}
lane.maxwell
  • 5,002
  • 1
  • 20
  • 30
  • Yes looks way better. I am quite new to spring (basically have not used java much in the last years, beside of selenium testing) so i do not know all those smart annotations yet. I will try out this code later (no IDE on this computer). Thank you very much! – AsconX Apr 18 '17 at 05:58
  • 2
    I get the following exception now: "java.lang.IllegalStateException: Failed to load ApplicationContext" Some research told me to create an xml file for the context. But the application itself runs without doing that. Do i really need to add the file to the project just for testing, or is there another smart annotation that i might use? :) – AsconX Apr 18 '17 at 07:28
  • What i tried so far (did not change anything): Adding @ContextConfiguration(classes = MainApp.class) And adding @SpringBootTest(...) does not work because then there are multiple context definitions. – AsconX Apr 18 '17 at 08:37
  • I found out that this error is caused by some completely other controller: `Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'graphEndpoint.dataConnection.repository.CustomerRepository'` - but this repository is not used in MainApp, which is the only controller i want to test for now. Any idea how i can make the test not taking care of that? – AsconX Apr 18 '17 at 09:05
  • I have no idea why, but removing @ComponentScan from MainApp solved this. Thank you very much! – AsconX Apr 18 '17 at 11:08