0

I am developing a rest api with spring boot and spring security.

the code looks like so:

@RestController
@RequestMapping(path = "/api")
@PreAuthorize("isAuthenticated()")
public class RestController {

  @GetMapping(path = "/get", produces = "application/json")
  public ResponseEntity<InDto> get(
      @AuthenticationPrincipal final CustomUser user) {

    // ...

    return ResponseEntity.ok(outDto);
  }
}


public class CustomUser {
    // does not inherit from UserDetails
}


public class CustomAuthenticationFilter extends OncePerRequestFilter {

  @Override
  protected void doFilterInternal(
    @NonNull final HttpServletRequest request,
    @NonNull final HttpServletResponse response,
    @NonNull final FilterChain filterChain)
    throws ServletException, IOException {

    if (/* condition */) {
      // ...
      final CustomUser user = new CustomUser(/* parameters */);

      final Authentication authentication =
          new PreAuthenticatedAuthenticationToken(user, "", new ArrayList<>());
      SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    filterChain.doFilter(request, response);
  }
}

I would like to unit test the RestController class ideally without the security feature but I don't know how to inject a specific CustomUser object during test.

I have tried to manually add a user to the security context before each test (see below) but the user injected into the controller during test is not the mocked on.

@WebMvcTest(RestController.class)
@AutoConfigureMockMvc(addFilters = false)
class RestControllerTest {

  @Autowired private MockMvc mockMvc;
  private CustomerUser userMock;

  @BeforeEach
  public void skipSecurityFilter() {
    userMock = Mockito.mock(CustomUser.class);
    SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
    final Authentication auth = new PreAuthenticatedAuthenticationToken(userMock, null, List.of());
    SecurityContextHolder.getContext().setAuthentication(auth);
  }

  @Test
  void test() {
    mockMvc.perform(
            MockMvcRequestBuilders.get("/api/get")
                .contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk());

  }
}

What is wrong? How to inject the specific userMock into the controller to perform the test?

EDIT to test with @WithMockCustomUser

as suggested in the doc https://docs.spring.io/spring-security/reference/servlet/test/method.html#test-method-withsecuritycontext i have updated the test to:

@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithMockCustomUser {
}

@Service
public class WithMockCustomUserSecurityContextFactory
    implements WithSecurityContextFactory<WithMockCustomUser> {

  @Override
  public SecurityContext createSecurityContext(final WithMockCustomUser customUser) {
    final SecurityContext context = SecurityContextHolder.createEmptyContext();

    final Authentication auth =
        new PreAuthenticatedAuthenticationToken(Mockito.mock(IUser.class), null, List.of());
    context.setAuthentication(auth);
    return context;
  }
}

@WebMvcTest(RestController.class)
@AutoConfigureMockMvc(addFilters = false)
class RestControllerTest {

  @Autowired private MockMvc mockMvc;
  private CustomerUser userMock;

  @BeforeEach
  public void skipSecurityFilter() {
    userMock = Mockito.mock(CustomUser.class);
  }

  @Test
  @WithMockCustomUser
  void test() {
    mockMvc.perform(
            MockMvcRequestBuilders.get("/api/get")
                .contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk());

  }
}

but the user object in the controller is still not the mock (created in the factory)

vivi
  • 163
  • 13
  • Does this answer your question? [@WithMockUser with custom User implementation](https://stackoverflow.com/questions/52861849/withmockuser-with-custom-user-implementation) – Eleftheria Stein-Kousathana Oct 11 '22 at 11:32
  • thanks for the link. It is very interesting however when implementing @WithMockUser the user provided to the controller is not the proper one – vivi Oct 11 '22 at 13:14

1 Answers1

1

I rewrote the test to initialise the security context within the test

@WebMvcTest(RestController.class)
@AutoConfigureMockMvc
@Import(value = {
    CustomAuthenticationFilter.class
})
class RestControllerTest {

  @Autowired private MockMvc mockMvc;
  private CustomerUser userMock;

  @BeforeEach
  public void skipSecurityFilter() {
    userMock = Mockito.mock(CustomUser.class);
  }

  @Test
  void test() {
    PreAuthenticatedAuthenticationToken(userMock, null, List.of());
    SecurityContextHolder.getContext().setAuthentication(auth);
    mockMvc.perform(MockMvcRequestBuilders.get("/api/get").contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk());
  }
}

and it works.

Not sure exactly why the it does not work with the @BeforeEach.

vivi
  • 163
  • 13