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.
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.