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.
@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
// ...
}
@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 of mockEmployee()
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.
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);
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With