Hamza Khadhri
Hamza Khadhri

Reputation: 217

How to mock spring security cookies session in spring boot unit Test?

I have added Http cookie Authentication using authentication manager to my Spring Boot REST API I have a controller that exposes a rest service allowing authentication to /api/auth/signin resource via Spring security cookies session.

Here is the the Controller and the security configuration This exemple.

After running the application, I noticed that it is important to carry out the unit test part, so I wanted to create mocks for the authenticateUser method (resource: /signin), but unfortunately I encountered problems.

Voici la classe AuthControllerTest:

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes=Application.class)
@WebMvcTest(AuthController.class)
public class AuthControllerTest {

    @MockBean
    UserRepository userRepository;
    @MockBean
    AuthenticationManager authenticationManager;
    @MockBean
    private UserDetailsServiceImpl userDetailsServiceImpl;

    @Autowired
    private MockMvc mockMvc;

    private static UserDetailsImpl dummy;

    @MockBean
    private JwtUtils jwtUtil;
    @Autowired
    WebApplicationContext webApplicationContext ;

    private ResponseCookie cookies;


    @BeforeEach
    public void setUp() {
        dummy = new UserDetailsImpl(10L,"test1","[email protected]","123456",new ArrayList<>());
        Authentication authentication = authenticationManager
                .authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));

        SecurityContextHolder.getContext().setAuthentication(authentication);

        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();*/

        cookies = jwtUtil.generateJwtCookie(dummy) ;
    }


    @Test
    @DisplayName("POST /signin")
    void authenticateUser() throws Exception
    {
        LoginRequest authenticationRequest = new LoginRequest("mod", "123456") ;
        String jsonRequest = asJsonString(authenticationRequest);

        RequestBuilder request = MockMvcRequestBuilders
                .post("/api/auth/signin")
                .content(jsonRequest)
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .accept(MediaType.APPLICATION_JSON);
        Authentication auth = Mockito.mock(Authentication.class);
        Mockito.when(auth.getName()).thenReturn("authName");
        auth.setAuthenticated(true);
        Mockito.when(auth.isAuthenticated()).thenReturn(true);
        Mockito.when(authenticationManager.authenticate(auth)).thenReturn(auth); // Failing here
        Mockito.when(jwtUtil.generateJwtCookie(dummy)).thenReturn(cookies);
        Mockito.when(userDetailsServiceImpl.loadUserByUsername("test1")).thenReturn(dummy);
        MvcResult mvcResult = mockMvc.perform(request)
                .andExpect(status().is2xxSuccessful())
                .andReturn();
    }
    public static String asJsonString(final Object obj) {
        try {
            return new ObjectMapper().writeValueAsString(obj);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
}

Here is the encountered errors after running the class AuthControllerTest:

java.lang.AssertionError: Range for response status value 403 expected: but was:<CLIENT_ERROR> Expected :SUCCESSFUL Actual :CLIENT_ERROR

at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59) at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122) at org.springframework.test.web.servlet.result.StatusResultMatchers.lambda$is2xxSuccessful$3(StatusResultMatchers.java:78) at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:212) at AuthControllerTest.authenticateUser(AuthControllerTest.java:102) 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.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725) at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84) at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115) at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)

Upvotes: 1

Views: 1431

Answers (1)

Elbashir Saror
Elbashir Saror

Reputation: 352

If you willing to change your code, then do this and hopefully everything will work fine:

A. Create a package in your test main package, it should include both words test and integration

package com.<yourApplication>.test.integration;

B.This is how your test class should be like:

@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Import({ ObjectMapper.class, <YourController>.class })
@TestMethodOrder(OrderAnnotation.class)
class YourTestClass {
    @Autowired
    private MockMvc mockMvc;
    @Autowired
    private ObjectMapper objectMapper;

    // user authentication
    private static String jwt; // can use this for your next test request
    @Test
    @Order(1)
    @DisplayName("User Authentication token")
    void authenticationTest() throws JsonProcessingException, Exception {
        final String link = "/api/auth/signin";
        AuthenticationRequest defaultAuth = new AuthenticationRequest("admin", "admin");
        
        System.out.println(objectMapper.writeValueAsString(defaultAuth));
        // perform the request
        MvcResult result = this.mockMvc
            .perform(MockMvcRequestBuilders.post(link)
            .accept(MediaType.APPLICATION_JSON)
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsBytes(defaultAuth)))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andReturn();
        String response = result.getResponse().getContentAsString();
        System.out.println("from response: " + response); //
        
        JsonNode root = objectMapper.readTree(response);
        JsonNode jwtvalue = root.get("jwt");
        jwt = jwtvalue.textValue();
        System.out.println("jwt deserlized: " + jwt);

    }

}

C. If the request returned an error, then the problem is either in your controller or the way you setup the JWT authentication.

Upvotes: 1

Related Questions