2

I'm using Spring Security, and facing issue writing unit test case (using MockMvc) for a controller.

I have a method in my controller that goes something like this:

@GetMapping
public ResponseEntity<User> getUser(@AuthenticationPrincipal User activeUser){
    String userEmail = activeUser.getEmail();
    return userService.getUser(userEmail);
}

I get a 500 error with this.

Another variation for the controller I've tried is, and this is working on Postman/Curl :

@GetMapping
public ResponseEntity<User> getUser(OAuth2Authentication authentication){
    String userEmail = (String) authentication.getUserAuthentication().getPrincipal();
    return userService.getUser(userEmail);
}

My service looks like :

public ResponseEntity<User> getUser(String email) {
    return userRepository.findByEmail(email)
            .map(record -> ResponseEntity.ok().body(record))
            .orElse(ResponseEntity.notFound().build());
}

In my unit test case for this controller method, I have:

@Test
@WithMockUser(username = "1", password = "pwd", roles = "USER")
public void controller_should_get_user() throws Exception {
    when(userService.getUser("1")).thenReturn(new ResponseEntity(userMock, HttpStatus.OK));
    this.mockMvc.perform(MockMvcRequestBuilders.get("/api/user/")).andExpect(status().isOk());
}

I am getting the following error:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
at com.timecloud.user.controller.UserControllerTest.controller_should_get_user(UserControllerTest.java:60)

How should I go about passing or mocking a user with the current authentication? Thanks.

anz
  • 987
  • 7
  • 21

2 Answers2

3

@WithMockUser creates a UsernameAuthenticationToken, not an OAuth2Authentication.

At least three solutions here:

  1. Inject an OAuth2Authentication mock or instance in the security context
  2. change your method to public ResponseEntity<User> getUser(Authentication authentication), then using authentication.getName() inside
  3. use some existing tooling to apply solution 1. for you, like in this libs I wrote

Sample usage with solution 1

@Test
public void test() throws Exception {
    final var storedRequest = mock(OAuth2Request);

    final var principal = mock(Principal.class);
    when(principal.getName()).thenReturn("user");

    final var userAuthentication = mock(Authentication.class);
    when(userAuthentication.getAuthorities()).thenReturn(Set.of(new SimpleGrantedAuthority("ROLE_USER"));
    when(userAuthentication.getPrincipal()).thenReturn(principal);

    final var oauth2Authentication = new OAuth2Authentication(storedRequest, authentication);

    SecurityContextHolder.getContext().setAuthentication(oauth2Authentication);

    // use MockMvc to test a @Controller or unit-test any other secured @Component as usual
}

Sample usage with solution 3

@Test
@WithMockAuthentication(authType = OAuth2Authentication.class, name = "user", authorities = "ROLE_USER")
public void test() throws Exception {
    // use MockMvc to test a @Controller or unit-test any other secured @Component as usual
}
ch4mp
  • 6,622
  • 6
  • 29
  • 49
1

NullPointerException is coming because your test is unable to find anything for OAuth2Authentication Object. There are two things you can do your test case:

  1. Try Mocking OAuth2Authentication in some setUp method.

OR

  1. If you are using Spring 4.0+, the best solution is to annotate the test method with @WithMockUser

    @Test @WithMockUser(username = "user1", password = "pwd", roles = "USER") public void mytest1() throws Exception { //Your test scenario }

Yogendra Mishra
  • 2,399
  • 2
  • 13
  • 20
  • Updated my answer, with the use of @WithMockUser and AuthenticationPrincipal (instead of OAuth2Authentication). I'm still facing the same issue. – anz Mar 14 '19 at 04:57
  • can your share code for your user-service. may be you need to change your mocking for user service to :- `when(userService.getUser("1")).thenReturn(userMock);` – Yogendra Mishra Mar 14 '19 at 05:07
  • updated. But the whole point of mocking the service was to not care about the underlying implementation, isn't it? – anz Mar 14 '19 at 05:13
  • correct but with mockmvc you are doing integration testing. check this out: https://stackoverflow.com/questions/15203485/spring-test-security-how-to-mock-authentication for ref. – Yogendra Mishra Mar 14 '19 at 05:23
  • 1.) `@WithMockUser` creates a `UsernameAuthenticationToken`, not an `OAuth2Authentication` and 2.) one can make unit (not integration) testing with mockmvc if mocking / stubing all dependencies (with `@MockBean` for instance) – ch4mp Dec 09 '21 at 20:36