Unit Testing rest controller with #AuthenticationPrincipal - java

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)

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.

Related

How to pass #MockBean to an internal function call from a JUnit?

I need to write unit tests for a Spring Controller class.
The setup is like this:
#RestController
#RequestMapping("/")
public class MyCustomController {
#Autowired
private MagicWriter magicWriter;
#Autowired
private MagicUpdater magicUpdater;
#RequestMapping(path = "/", method = RequestMethod.POST)
public String postMagicMethod(#RequestParam(name = "SomeParam") String param1) {
var magicHandler = new MagicHandler(magicWriter, magicUpdater);
return magicHandler.doSomeMagic();
}
}
From my JUnit test, I need to use #MockBean for magicWriter and magicUpdater class.
So far I could not find anything constructive.
Here is my Unit test
#SpringJUnitConfig
#WebMvcTest(value= MyCustomController.class)
public class MyCustomControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private MagicWriter magicWriter;
#MockBean
private MagicUpdater magicUpdater;
#Autowired
private WebApplicationContext webApplicationContext;
#Configuration
static class Config {
#Bean
MyCustomController dispatchController() {
return new MyCustomController();
}
}
#Test
void basicTest() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
HttpHeaders headers = new HttpHeaders();
// Added some http headers
String uri = "/";
RequestBuilder request = MockMvcRequestBuilders.request(HttpMethod.POST, uri, headers);
MvcResult result = mockMvc.perform(request).andReturn();
assertThat(result.getResponse().getContentAsString()).isEqualTo(expected);
}
}
Convert your #Autowired parameters to be constructor based and not field-based.
#RestController
#RequestMapping("/")
public class MyCustomController {
private MagicWriter magicWriter;
private MagicUpdater magicUpdater;
#Autowired
public MyCustomController(MagicWriter magicWriter, MagicUpdater magicUpdater) {
this.magicWriter = magicWriter;
this.magicUpdater = magicUpdater;
}
// ... rest of your code
}
Then in your test, you just new an instance of this class with your mocks passed in. You're already resigned to using mock beans, so you don't need to whole Spring Context to come along.
// Unit test code example
MyCustomController testObject;
MagicWriter magicWriterMock;
magicUpdater magicUpdaterMock;
#BeforeEach
void setUp() throws Exception {
magicWriterMock = mock(MagicWriter.class);
magicUpdaterMock = mock(MagicUpdater.class);
testObject = new MyCustomController(magicWriterMock, magicUpdaterMock);
}

Spring Unit Test Rest Controller By Setting Private Fields

I have a simple Rest Controller as below
#RestController
public class HealthController {
private static final CustomLogger logger = CustomLogger.getLogger(HealthController.class.getName());
private HealthService healthService;
#Autowired
public HealthController(HealthService healthService) {
this.healthService = healthService;
}
#RequestMapping(value = "/health", method = RequestMethod.GET)
public ResponseEntity<?> healthCheck() {
return healthService.checkHealth();
}
}
The service class is below
#Service
public class HealthService {
private static final CustomLogger logger = CustomLogger.getLogger(HealthController.class.getName());
public ResponseEntity<?> checkHealth() {
logger.info("Inside Health");
if (validateHealth()) {
return new ResponseEntity<>("Healthy", HttpStatus.OK);
} else {
return new ResponseEntity<>("Un Healthy", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
boolean validateHealth() {
return true;
}
}
The corresponding unit test for the controller class as below
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = HealthController.class)
public class HealthControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private HealthService healthService;
#Test
public void checkHealthReturn200WhenHealthy() throws Exception {
ResponseEntity mockSuccessResponse = new ResponseEntity("Healthy", HttpStatus.OK);
when(healthService.checkHealth()).thenReturn(mockSuccessResponse);
RequestBuilder requestBuilder = MockMvcRequestBuilders.get(
"/health").accept(
MediaType.APPLICATION_JSON);
MvcResult healthCheckResult = mockMvc
.perform(requestBuilder).andReturn();
Assert.assertEquals(HttpStatus.OK.value(), healthCheckResult.getResponse().getStatus());
}
}
The problem I have is my CustomLogger. Since it has external dependencies am having issues in trying to test this.The same kind of logger is present in my service classes too.
How can I test such a class. I tried the below stuffs
Created a custom class name CustomLoggerForTest under test. Used
ReflectionTestUtils.setField(healthService, "logger", new CustomerLoggerForTest(HealthService.class.getName()));
in the setUp. But it did not help. Using this we cannot set the static fields hence tried even converting them to be non-static
Tried with mocking the CustomLogger in setup as below
mockStatic(CustomLogger.class); when(CustomLogger.getLogger(any())) .thenReturn(new CustomLoggerForTest(HealthController.class.getName()));
But no luck.
Is there anything that am doing wrong that is causing this?

No mapping for request with mockmvc

Currently struggling with problem when I get 'mapping error for request' with following controller/test configuration.
Controller:
#Slf4j
#Validated
#RestController
#RequiredArgsConstructor
public class AdtechController {
private final AdtechService adtechService;
#PostMapping(value = "/subscriber/session")
public ResponseEntity<ResponseDto> submitSession(#RequestBody RequestDto requestDto) {
log.trace("execute submitSession with {}", requestDto);
ResponseDtoresponse = adtechService.submitSession(requestDto);
return new ResponseEntity<>(response, HttpStatus.OK);
}
#ExceptionHandler(AdtechServiceException.class)
public ResponseEntity<AdtechErrorResponse> handleAdtechServiceException(AdtechServiceException e) {
return new ResponseEntity<>(new AdtechErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Test:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
#SpringJUnitConfig({AdtechTestConfig.class})
public class AdtechControllerTest {
private static final ObjectMapper OBJECT_MAPPER = JsonUtil.getJackson();
#Autowired
private MockMvc mockMvc;
#Test
public void testSubmitSession() throws Exception {
RequestDto requestDto = new RequestDto ();
requestDto.setKyivstarId("1123134");
requestDto.setMsisdn("123476345242");
requestDto.setPartnerId("112432523");
requestDto.setPartnerName("125798756");
String request = OBJECT_MAPPER.writeValueAsString(requestDto);
System.out.println("REQUEST: " + request);
String response = OBJECT_MAPPER.writeValueAsString(new ResponseDto("123"));
System.out.println("RESPONSE: " + response);
mockMvc.perform(post("/subscriber/session")
.content(MediaType.APPLICATION_JSON_VALUE)
.content(request))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString(response)));
}
}
Configuration:
#Configuration
public class AdtechTestConfig {
#Bean
public AdtechService adtechTestService() {
return requestDto -> new AdtechResponseDto("123");
}
}
After test execution I get No mapping for POST /subscriber/session
The reason for the struggle is that my code from other modules with the same configuration works fine. Can somebody point out what am I missing ? Thanks in advance!
Apparently you are loading a configuration class to mock beans, this interferes with the other parts of Spring Boot and probably leads to partially loading your application. I suspect only the mocked service is available.
Instead of the test configuration use #MockBean to create a mock for the service and register behaviour on it.
#SpringBootTest
#AutoConfigureMockMvc
public class AdtechControllerTest {
private static final ObjectMapper OBJECT_MAPPER = JsonUtil.getJackson();
#Autowired
private MockMvc mockMvc;
#MockBean
private AdtechService mockService;
#BeforeEach
public void setUp() {
when(mockService.yourMethod(any()).thenReturn(new AdtechResponseDto("123"));
}
#Test
public void testSubmitSession() throws Exception {
// Your original test method
}
}
If the only thing you want to test is your controller you might also want to consider using #WebMvcTest instead of #SpringBootTest.
#WebMvcTest(AdTechController.class)
public class AdtechControllerTest {
private static final ObjectMapper OBJECT_MAPPER = JsonUtil.getJackson();
#Autowired
private MockMvc mockMvc;
#MockBean
private AdtechService mockService;
#BeforeEach
public void setUp() {
when(mockService.yourMethod(any()).thenReturn(new AdtechResponseDto("123"));
}
#Test
public void testSubmitSession() throws Exception {
// Your original test method
}
}
This will load a scaled-down version of the context (only the web parts) and will be quicker to run.
try this:
#Slf4j
#Validated
#RestController
#RequiredArgsConstructor
public class AdtechController {
private AdtechService adtechService;
public AdtechController (AdtechService adtechService) {
this.adtechService= adtechService;
}
#PostMapping(value = "/subscriber/session")
public ResponseEntity<ResponseDto> submitSession(#RequestBody RequestDto requestDto) {
log.trace("execute submitSession with {}", requestDto);
ResponseDtoresponse = adtechService.submitSession(requestDto);
return new ResponseEntity<>(response, HttpStatus.OK);
}
#ExceptionHandler(AdtechServiceException.class)
public ResponseEntity<AdtechErrorResponse> handleAdtechServiceException(AdtechServiceException e) {
return new ResponseEntity<>(new AdtechErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Test:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
#SpringJUnitConfig({AdtechTestConfig.class})
public class AdtechControllerTest {
private static final ObjectMapper OBJECT_MAPPER = JsonUtil.getJackson();
#Autowired
private MockMvc mockMvc;
#Autowired
private AdtechService adtechService;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
this.mvc = MockMvcBuilders.standaloneSetup(new AdtechController(adtechService)).build();
}
#Test
public void testSubmitSession() throws Exception {
RequestDto requestDto = new RequestDto ();
requestDto.setKyivstarId("1123134");
requestDto.setMsisdn("123476345242");
requestDto.setPartnerId("112432523");
requestDto.setPartnerName("125798756");
String request = OBJECT_MAPPER.writeValueAsString(requestDto);
System.out.println("REQUEST: " + request);
String response = OBJECT_MAPPER.writeValueAsString(new ResponseDto("123"));
System.out.println("RESPONSE: " + response);
mockMvc.perform(post("/subscriber/session")
.content(MediaType.APPLICATION_JSON_VALUE)
.content(request))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString(response)));
}
}
Is the AdtechTestConfig.class introducing the /ad-tech path segment in to your test request? If so, this is why your test is trying the path /ad-tech/subscriber/session instead of /subscriber/session.
If this is actually the correct uri, then you may add #RequestMapping to the controller like below or just to the post method itself
#Slf4j
#Validated
#RestController
#RequestMapping("/ad-tech")
#RequiredArgsConstructor
public class AdtechController {
private final AdtechService adtechService;
#PostMapping(value = "/subscriber/session")
public ResponseEntity<ResponseDto> submitSession(#RequestBody RequestDto requestDto) {
...

How to mock SpringSecurity and OncePerRequestFilter (JWT Authentication Filter) to get #AuthenticationPrincipal?

can anyone tell me how to mock SpringSecurity and OncePerRequestFilter (JWT Authentication Filter)?
There are examples on the Internet, but something I can not collect all together.
So, there is SpringBoot 2.0.
There is a user who comes from #AuthenticationPrincipal as function parameter.
There is a JWtFilter extends OncePerRequestFilter which checks the validity of the jwt token.
Necessary
#SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = NfaBackendApplication.class)
#TestExecutionListeners({
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
WithSecurityContextTestExecutionListener.class })
public class OfferControllerTest extends AbstractTestNGSpringContextTests
{
private MockMvc mockMvc;
#Autowired
private WebApplicationContext context;
#MockBean
private UserRepository kvUserRepository;
#InjectMocks
private JWTAuthenticationFilter jwtAuthenticationFilter = new JWTAuthenticationFilter();
#Mock
private JWTokenProvider jwtTokenProvider;
#BeforeClass
public void setUp() throws ServletException, IOException
{
mockMvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity())
.build();
MockitoAnnotations.initMocks(this);
}
#Test(enabled = true)
public void getExternalOffers() throws Exception
{
// KVUserDetails user = new KVUserDetails("mail#mail.ua", "test", false, true, AuthorityUtils
// .createAuthorityList("ADMIN"));
// TestingAuthenticationToken testingAuthenticationToken = new TestingAuthenticationToken(user, null);
// SecurityContextHolder.getContext().setAuthentication(testingAuthenticationToken);
MockFilterChain filterChain = new MockFilterChain();
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader(HttpHeaders.AUTHORIZATION,"Authorized");
MockHttpServletResponse response = new MockHttpServletResponse();
when(jwtTokenProvider.validateToken("Authorized")).thenReturn(true);
mockMvc.perform(get("/admin/offers")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
Tell me please the working example how to wet SpringSecurity and OncePerRequestFilter (JWT Authentication Filter)?
That at request there will come the user which AuthenticationPrincipal.
Thanks.
You can use #WithUserDetails to initialize user for MockMvc test. No need to mock servlet filter.
Something like this:
#Test(enabled = true)
#WithUserDetails("mail#mail.ua")
public void getExternalOffers() throws Exception
{

How do I test a custom Spring Boot Actuator endpoint?

I have a spring boot actuator and WebMvc test isn't working. It returns an empty body. How would I test this?
#Configuration
#ManagementContextConfiguration
public class TestController extends AbstractMvcEndpoint
{
public TestController()
{
super( "/test", false, true );
}
#GetMapping( value = "/get", produces = MediaType.APPLICATION_JSON_VALUE )
#ResponseBody
public OkResponse getInfo() throws Exception
{
return new OkResponse( 200, "ok" );
}
#JsonPropertyOrder( { "status", "message" } )
public static class OkResponse
{
#JsonProperty
private Integer status;
#JsonProperty
private String message;
public OkResponse(Integer status, String message)
{
this.status = status;
this.message = message;
}
public Integer getStatus()
{
return status;
}
public String getMessage()
{
return message;
}
}
}
When I try to test it with the below, it doesn't work. I get an empty body in the return.
#RunWith( SpringJUnit4ClassRunner.class )
#DirtiesContext
#WebMvcTest( secure = false, controllers = TestController.class)
#ContextConfiguration(classes = {TestController.class})
public class TestTestController
{
private MockMvc mockMvc;
private ObjectMapper mapper;
#Autowired
private WebApplicationContext webApplicationContext;
#Before
public void setup()
{
//Create an environment for it
mockMvc = MockMvcBuilders.webAppContextSetup( this.webApplicationContext )
.dispatchOptions( true ).build();
mapper = new ObjectMapper();
}
#SpringBootApplication(
scanBasePackageClasses = { TestController.class }
)
public static class Config
{
}
#Test
public void test() throws Exception
{
//Get the controller's "ok" message
String response = mockMvc.perform(
get("/test/get")
).andReturn().getResponse().getContentAsString();
//Should be not null
Assert.assertThat( response, Matchers.notNullValue() );
//Should be equal
Assert.assertThat(
response,
Matchers.is(
Matchers.equalTo(
"{\"status\":200,\"message\":\"ok\"}"
)
)
);
}
}
Here's a test I wrote to do something like what you are talking about. This test validates that our only the actuator endpoints we want to expose are available.
This is using SpringBoot 1.5.
I found the question here helpful: Unit testing of Spring Boot Actuator endpoints not working when specifying a port.
#RunWith(SpringRunner.class)
#SpringBootTest
#TestPropertySource(properties = {
"management.port="
})
public class ActuatorTests {
#Autowired
private WebApplicationContext context;
#Autowired
private FilterChainProxy springSecurityFilterChain;
private MockMvc mvc;
#Before
public void setup() {
context.getBean(MetricsEndpoint.class).setEnabled(true);
mvc = MockMvcBuilders
.webAppContextSetup(context)
.alwaysDo(print())
.apply(SecurityMockMvcConfigurers.springSecurity(springSecurityFilterChain))
.build();
}
#Test
public void testHealth() throws Exception {
MvcResult result = mvc.perform(get("/health")
.with(anonymous()))
.andExpect(status().is2xxSuccessful())
.andReturn();
assertEquals(200, result.getResponse().getStatus());
}
#Test
public void testRestart() throws Exception {
MvcResult result = mvc.perform(get("/restart")
.with(anonymous()))
.andExpect(status().is3xxRedirection())
.andReturn();
assertEquals(302, result.getResponse().getStatus());
assertEquals("/sso/login", result.getResponse().getRedirectedUrl());
}
}

Categories

Resources