Peter Penzov
Peter Penzov

Reputation: 1588

Spring Boot : Mock user authentication with security context using Junit

I want to create JUnit 5 test for Rest API which uses JWT token validation:

    @Test
    public void resetTokenTest_OK() throws Exception {
        when(userService.findByResetPasswordToken(anyString())).thenReturn(trueOptional);

        mockMvc.perform(post("/users/reset_token")
                .contentType(MediaType.APPLICATION_JSON)
                .content(ResetPasswordTokenDTO))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.resetPasswordToken").value(randomString_254))
                .andExpect(jsonPath("$.login").value("login"))
                .andExpect(jsonPath("$.status").value("200"))
                .andExpect(jsonPath("$.error").value(""))
                .andExpect(jsonPath("$.errorDescription").value(""));
    }

GitHub

I make a Mockup request using this payload:

{
  "resetPasswordToken": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.VFb0qJ1LRg_4ujbZoRMXnVkUgiuKq5KxWqNdbKq_G9Vvz-S1zZa9LPxtHWKa64zDl2ofkT8F6jBt_K4riU-fPg",
  "login": "login",
  "status": "1",
  "error": "",
  "errorDescription": ""
}

I get NPE for this line:

Caused by: java.lang.NullPointerException
    at org.engine.utils.GenericUtils.getLoggedInUser(GenericUtils.java:15)
    at org.engine.rest.UsersController.lambda$resetToken$0(UsersController.java:159)
    at java.base/java.util.Optional.map(Optional.java:258)
    at org.engine.rest.UsersController.resetToken(UsersController.java:153)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)

Full Log:

I get NPE for this code:

return (Users) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

GitHub

How I can mock the result from SecurityContextHolder.getContext().getAuthentication().getPrincipal() in order not to get NPE?

Upvotes: 2

Views: 5896

Answers (1)

Romil Patel
Romil Patel

Reputation: 13727

There are multiple ways to mock the security using @WithMockUser, @WithAnonymousUser, @WithUserDetails, @WithSecurityContext. You can use these annotations with @Test method


@WithMockUser

  • The user with the username "user" does not have to exist since we are mocking the user
  • The Authentication that is populated in the SecurityContext is of type UsernamePasswordAuthenticationToken
  • The principal on the Authentication is Spring Security’s User object
  • The User will have the username of "user", the password "password", and a single GrantedAuthority named "ROLE_USER" is used.
  • You may customize the parameters such as username, roles, value

@WithAnonymousUser

Using @WithAnonymousUser allows running as an anonymous user. This is especially convenient when you wish to run most of your tests with a specific user, but want to run a few tests as an anonymous user. For example, the following will run withMockUser1 and withMockUser2 using @WithMockUser and anonymous as an anonymous user.


@WithUserDetails

While @WithMockUser is a very convenient way to get started, it may not work in all instances. For example, it is common for applications to expect that the Authentication principal be of a specific type. This is done so that the application can refer to the principal as the custom type and reduce coupling on Spring Security.

The custom principal is often times returned by a custom UserDetailsService that returns an object that implements both UserDetails and the custom type. For situations like this, it is useful to create the test user using the custom UserDetailsService. That is exactly what @WithUserDetails does.


@WithSecurityContext

We can create our own annotation that uses the @WithSecurityContext to create any SecurityContext we want. For example, we might create an annotation named @WithMockCustomUser as @WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)

Upvotes: 7

Related Questions