0

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","test1@mail.com","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)

Hamza Khadhri
  • 207
  • 4
  • 19
  • You need to print the response to see the actual error, or simple do fetch request and see if it's working probably. Your UserDetails implementations might have an error, like if active, accountNonLocked, or isCredentialsNonExpired is set to false. – Elbashir Saror Nov 04 '22 at 09:49

1 Answers1

1

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.

  • i got this error: java.lang.IllegalStateException: Unable to find a SpringBootConfiguration, you need to use ContextConfiguration or SpringBootTest(classes=...) with your test – Hamza Khadhri Nov 04 '22 at 10:48
  • This is because you didn't create an integration test package perhaps. – Elbashir Saror Nov 04 '22 at 10:57
  • 1
    Also, add the word Integration to your class name – Elbashir Saror Nov 04 '22 at 11:02
  • Can you explain me why ? what i know was just adding Test word at the end of the word ? also i want to know is it unit test or Integration Test ? – Hamza Khadhri Nov 04 '22 at 13:24
  • it works finally, well your code helped me to understand.In my case, i'm not implementing with jwt, however, im implementing cookies session, so to be sure if the cookies was generated i tried this after getting the result, String cookie = result.getResponse().getCookie("bezkoder").getValue(); – Hamza Khadhri Nov 04 '22 at 14:19
  • 1
    Yes, I also thought you are using cookies session, but I think the testing approach is the same. It's integration test. It's better cause you are testing and importing multiple classes. It is more straightforward than Unit test, because you don't have to import and mock every component needed for your test. Unit test is better used to test one class or so, in example you can mock your repository and test your service, and call it Service layer test, or mock your service and test your Controller, it's also good for testing configuration, like testing cache manger, circuit breaker...etc. – Elbashir Saror Nov 04 '22 at 15:37
  • You might have used components test for this, which similar to Integration test. It is used to test how your internal application components function together. Generally, Integration test will save you time, and has less error percentage. – Elbashir Saror Nov 04 '22 at 15:38