Mock SecurityContextHolder / Authentication always returning null
Solution 1
I ended up using MockMvc
despite the app not being Spring MVC-based. Additionally, I separated the SecurityContext
calls into another service, but before doing that I could assert that the @WithMockUser
annotation was working properly.
What's key for this to work is using these snippets at class level:
@WebMvcTest(MeController.class)
@Import({ControllerConfiguration.class, BeanConfiguration.class})
public class MeControllerTest {
// ...
}
Using @WebMvcTest
facilitates not having to initialize a SecurityContext
in the first place. You don't even have to call springSecurity()
. You can just just the mockMvc.perform()
operations as usual, and any calls to the SecurityContext
will return whatever mocked user you specify, either with @WithMockUser
or mocking the service that handles such a call.
Solution 2
Easier Way of writing Junit for Authentication SecurityContextHolder would be to mock them. Following is the working implementation of it. You can add the mock classes as per your need and then set context of SecurityContextHolder and then use when() to further mock and return proper mock value.
AccessToken mockAccessToken = mock(AccessToken.class);
Authentication authentication = mock(Authentication.class);
SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getAuthentication()).thenReturn(authentication);
SecurityContextHolder.setContext(securityContext);
when(SecurityContextHolder.getContext().getAuthentication().getDetails()).thenReturn(mockSimpleUserObject);
Manuel Santiago Yépez
Usually Java developer, but I cheat on it with C# or RoR as my sidekicks.
Updated on June 21, 2022Comments
-
Manuel Santiago Yépez about 2 years
I'm aware this question gets asked a lot, but maybe I have some things that are particular to this. I'm trying to do some integration tests on a Spring Boot application that supports REST (not Spring MVC) and for some reason
SecurityContextHolder.getContext().getAuthentication()
always returns null, even when using@WithMockUser
on the test. I'm not certain if this has to do with using profiles on the configuration classes, but so far we haven't had troubles with this.Class
@Override public ResponseEntity<EmployeeDTO> meGet() { Principal principal = SecurityContextHolder.getContext().getAuthentication(); logger.debug("Endpoint called: me({})", principal); EmployeeDTO result; // Get user email from security context String email = principal.getName(); // NPE here // ... }
Test
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {"eureka.client.enabled:false"}) @WithMockUser @ActiveProfiles(value = "test") public class MeControllerTest extends IntegrationSpringBootTest { @Autowired private TestRestTemplate restTemplate; @MockBean private SecurityContext securityContext; @MockBean private Authentication authentication; @MockBean private EmployeeRepository employeeRepository; @BeforeClass public static void setUp() { } @Before @Override public void resetMocks() { reset(employeeRepository); } @Test public void meGet() throws Exception { when(securityContext.getAuthentication()).thenReturn(authentication); securityContext.setAuthentication(authentication); when(authentication.getPrincipal()).thenReturn(mockEmployee()); SecurityContextHolder.setContext(securityContext); when(employeeRepository.findByEmail(anyString())).thenReturn(mockEmployee()); ResponseEntity<EmployeeDTO> employeeDTOResponseEntity = this.restTemplate.getForEntity("/me", EmployeeDTO.class); // ... }
If I return a mock
Principal
instead ofmockEmployee()
the test cannot even start because this happens:org.springframework.beans.factory.BeanCreationException: Could not inject field: private org.springframework.security.core.Authentication com.gft.employee.controller.MeControllerTest.authentication; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'org.springframework.security.core.Authentication#0' is expected to be of type 'org.springframework.security.core.Authentication' but was actually of type '$java.security.Principal$$EnhancerByMockitoWithCGLIB$$657040e6'
Additional clarifications: This Spring Boot app also uses OAuth2 for authorization, but it must be turned off for these tests. That's why we use profiles. Omitting the
@ActiveProfiles
annotation gives us a 401 Unauthorized error against the endpoint request.I could use PowerMock but I would like to avoid it if possible.
-
afe over 3 yearssorry this is not clear for me. when I need to mock two different users how am I supposed to pass the mocked access-tokens?