Spring Boot MockMVC test - enable pre & post annotations - spring

I have a (kotlin) test that has this code with Spring Boot:
#Autowired
private lateinit var context: WebApplicationContext
#Autowired
private lateinit var entityManager: EntityManager
private var mockMvc: MockMvc? = null
#Autowired
lateinit var springSecurityFilterChain: Filter
#BeforeAll
fun setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.addFilters<DefaultMockMvcBuilder>(springSecurityFilterChain)
.apply<DefaultMockMvcBuilder>(springSecurity())
.build()
}
This method fails with 401:
#PutMapping("/register")
#ResponseStatus(HttpStatus.OK)
#PreAuthorize("permitAll()")
fun registerNewUser(#Valid #RequestBody userDTO: UserDTO): User {
val user = userDTO.toUser(passwordEncoder)
user.activationCode = generateActivationCode()
userRepository.save(user)
return user
}
I don't understand why my pre/post authorize is not running?
I have these defined in my app's src/main/:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
class MethodSecurityConfigurer : GlobalMethodSecurityConfiguration()
#Configuration
class SecurityConfiguration {
#Bean
fun getPasswordEncoder(): PasswordEncoder {
return Pbkdf2PasswordEncoder()
}
}
What do I need to do in MockMVC's setup to enable the pre/post annotations?

So I figured it out and thought I'd add the answer here.
It's not a MockMVC problem, and M. Deinum is correct in that the two annotations will work.
My issue was that #PreAuthorize("permitAll()") is a separate filter from the "normal" spring security filters, so I was not even getting to the method authorization aspect.
I configured Spring Security's HttpSecurity to allow the method, then the permitAll() starting working properly.
http.authorizeRequests().antMatchers(HttpMethod.PUT, '/register').permitAll()

Related

Spock Testing Framework with Spring Boot to test REST Controller

I have written rest api using spring boot this is controller code
#RestController
public class ApiController {
#Autowired
private ApiService apiService;
#PostMapping(path = "/user", consumes = {MediaType.APPLICATION_JSON_VALUE},
produces = {MediaType.APPLICATION_JSON_VALUE})
#LogTransaction
public ResponseEntity<String> user(HttpServletRequest request) {
//apiService.getUser() method call database so i need to mock it
USer user = apiService.getUser(request);
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(user);
}
}
This api is working fine and i have written test using spock framework in groovy
#SpringBootTest
class ApiControllerTest extends Specification {
#Autowired
private ApiController apiController;
#Autowired
private ApiService apiService;
#Autowired
MockMvc mockMvc
def "User api"() {
given:
def User user = new User()
apiService.getUser(_) >> user
when:
def result = mockMvc.perform(MockMvcRequestBuilders
.post("/user")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"username":"demo",
"password":"demo"
}
"""))
.andReturn()
then:
assert result.response.status == HttpStatus.OK.value()
}
This is working fine also. Method apiService.getUser(request) call database . I want to mock it . But it is calling each time to database. So am trying to mock apiService.getUser(request) using #Autowired here but it is not working for me and this method execute each time and call to database. Can anyone share the way to mock apiService.getUser(request) method of controller .
You can partially mock spring beans by annotating them with #SpringSpy.
So, in your case, all you have to do it replace #Autowired with #SpringSpy:
#SpringSpy
private ApiService apiService;
and it should work.
you can use a exclude for datasources like
#SpringBootTest
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
#AutoConfigureMockMvc
public class Class1Test{
#Autowired
private MockMvc mockMvc;
#MockBean
private RestTemplate restTemplate;
#MockBean
private IYourRepository repository;
#Autowired
private SecurityJwtSettings securityJwtSettings;
and with mockito you can mock a request to database:
Mockito.doAnswer(x->{
YourEntity entityToSave = x.getArgument(0);
entityToSave.setCreatedAt(LocalDateTime.now());
Assert.assertEquals("tester.admin.01",entityToSave.getAuthorizedBy());
Assert.assertEquals("tester.01", entityToSave.getRequestedBy());
return entityToSave;
}).when(repository).save(Mockito.any(YourEntity.class));

Mockito: How to mock WebClient configuring Beans

I'm using Mockio, Wiremock and WebClient and I want to test my service layer.
My goal is to use an instance of the webclient and do a real request to wiremock.
Therefore I have to use a standard configuration and not my oauth config from the production mode.
In the service class, I execute reuqets to another api. So the class under test ist annotated with #Service.
Here is the class:
#Service
public class UserServiceImpl implements UserService{
private final Logger log = Logger.getLogger(this.getClass().getName());
private final WebClient webClient;
private final ApplicationConstants applicationConstants;
public UserServiceImpl (WebClient webClient, ApplicationConstants applicationConstants) {
this.applicationConstants = applicationConstants;
this.webClient = webClient;
}
#Override
public User getUserById(#NotNull(message = "userId must not be null.") #NotBlank(message = "userId must not be blank.") String userId) {
return webClient.get()...
}
I configured my WebClient to use Oauth via two Bean Methods in a class annotated with #Configuration.
#Configuration
public class WebClientConfig {
#Bean
public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
...
}
/*
Manages the auth process and token refresh process
*/
#Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
...
}
}
Because I want to use the webClient without oauth to call wiremock, I want to replace the Beans to return a simple Webclient.builder().build();
So I did:
#ExtendWith({SpringExtension.class, WireMockExtension.class, MockitoExtension.class})
public class TestClass {
#Mock
WebClientConfig webClientConfig;
#MockBean
WebClient webClient;
#InjectMocks
UserServiceImpl userService;
In general as I understand Mockito, i would use my class under test ( the userServiceImpl) with #InjectMocks, so a real instance is used and the dependencies are injected. Therefor I have to provide a Mock for the Webclient. As I don't want to Mock the webclient and just want to configure it different, I do not have to use #Mock. Instead it should be somethig like #MockBean as this annotation creates a bean and replaces existing ones in the context. So I have to mock the Webclientconfig class with #Mock and define something like
when(webclientConfig).webclient(any(OAuth2AuthorizedClientManager.class)).thenReturn(Webclient.builder.build);
But this does not work as I always get a nullpointer exception on the call.
So the basic questions are:
Is my understanding of Mockito right?
How do I have to Manage the Webclient config?
Looks like a case of configuring MockBeans before the rest of the application starts up, which is answered here:
Configure #MockBean component before application start
As of this writing, the answer above mentions using #Primary or #MockBean(answer = Answers.CALLS_REAL_METHODS) as workarounds.

How to include custom security interceptor in spring boot test

I want to do some end-to-end test for spring boot rest-api application. To achieve this im using spring mock mvc. But i can't get the 200 response because the rest api is using custom security interceptor to validate the token in request. Instead i keep getting 401 as a response. How to include this token validation in my test?
I've tried several configuration by including #ContextConfiguration(classes = {WebMvcConfig.class}) in my test class. WebMvcConfig is configuration class to register the interceptor.
This is my test file
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
#SpringBootTest(classes = VeripalServiceApplication.class)
#TestPropertySource(locations="classpath:test.properties")
#Transactional
public class VeripalTextConfigurationTest {
#Autowired
private MockMvc mockMvc;
#Test
public void happpyPath_thenReturns200() throws Exception {
String jsonBody = "some json body";
String endPoint = "/end_point_to_my_api";
HttpHeaders headers = new HttpHeaders();
headers.add("token", "this_is_my_token");
headers.setContentType(aplication/json);
/** Hit the API */
mockMvc.perform(post(endPoint)
.headers(httpHeaders)
.content(jsonBody)
)
.andExpect(status().isOk()).andDo(print());
}
}
And this is the #Configuration
#Configuration
#EnableScheduling
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Autowired
private ConsumerService consumerService;
#Autowired
private EndpointService endpointService;
#Autowired
private ConsumerConfigurationService consumerConfigurationService;
#Autowired
private AccessLimitService accessLimitService;
#Autowired
private ConfigurationHistoryService configurationHistoryService;
#Autowired
private LimitCarryOverService limitCarryOverService;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new Interceptor(consumerService, endpointService, consumerConfigurationService, accessLimitService, configurationHistoryService, limitCarryOverService));
}
}
And this is my Interceptor class
public class Interceptor implements HandlerInterceptor {
// some code here ...
}
you need to have a clear picture of request life-cycle in Servlet API and Spring Security framework.
This article might help you to understand this flow http://blog.florian-hopf.de/2017/08/spring-security.html
So, I'm pretty sure, you have an issue in authentication filters, thus you can resolve it in couple ways:
Disable security, for example by using #AutoConfigureMockMvc(secure = false)
Or you need to mock some places (AuthenticationProvider, UserDetailsService, etc) where you can provide Authentication object
Or, it also might help, try to play with #WithMockUser
.
Related posts:
Spring Test & Security: How to mock authentication?
V2: use IoC + Mockito, e.g. stub it for unit tests. I don't see how your code are written, so I believe a snippet below might help you.
// #Import({MyAuthCustomInterceptor.class}) // eq to #Component/#Service to create a bean
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Autowired
MyAuthCustomInterceptor interceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor);
}
}
public class VeripalTextConfigurationTest {
#MockBean
MyAuthCustomInterceptor interceptor;
#SetUp
public void setup(){
Mockito.when(interceptor.preHandle(...)).thenReturn(true);
}
}

Mock SecurityContextHolder / Authentication always returning null

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 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.
This sample code is working for me. This code is using JUnit 5.
#SpringBootTest(classes = Application.class)
#AutoConfigureMockMvc //need this in Spring Boot test
public class LoginControllerIntegrationTest {
// mockMvc is not #Autowired because I am customizing it #BeforeEach
private MockMvc mockMvc;
#Autowired
private WebApplicationContext context;
#Mock
DefaultOidcUser principal;
#BeforeEach
public void beforeEach() {
Authentication authentication = mock(OAuth2AuthenticationToken.class);
// Mockito.whens() for your authorization object
SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getAuthentication()).thenReturn(authentication);
when(authentication.getPrincipal()).thenReturn(principal);
SecurityContextHolder.setContext(securityContext);
// setting mockMvc with custom securityContext
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
#Test
public void given_any_OAuth2AuthenticationToken_when_login_then_redirect_to_logout() throws Exception {
final String loginName = "admin";
// given
// manipulate the principal as needed
when(principal.getAttribute("unique_name")).thenReturn(loginName);
// #formatter:off
// when
this.mockMvc.perform(get("/login"))
.andDo(print())
//then
.andExpect(status().isFound())
.andExpect(redirectedUrl("/logout"));
// #formatter:off
}
}

Custom filter not injected in Spring integration test

I have an integration test that is designed to start my Spring Boot app:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = AppConfigCorrelationIdTestIt.class)
#WebIntegrationTest("server.port:0")
public class CorrelationIdTestIT {
Where the configuration class is:
#EnableConfigurationProperties
#ComponentScan
#EnableAutoConfiguration
#Configuration
public class AppConfigCorrelationIdTestIt {
In the app I have a defined custom servlet filter:
public class CorrelationHeaderFilter implements Filter {
But when testing my app I'm finding that the customer filter isn't instantiated and injected in to the filter chain. The only way round this I've found is to manually create it as a bean in AppConfigCorrelationIdTestIt, and then it works perfectly.
#Bean
public CorrelationHeaderFilter correlationHeaderFilter() {
return new CorrelationHeaderFilter();
}
Any ideas why the filter isn't picked up by Spring Boot when the application starts?
Thanks
Usually, in test classes possible to use DefaultMockMvcBuilder.addFilter(Filter filter, String... urlPatterns) when configured MockMvc. For example:
#WebAppConfiguration
#ContextConfiguration
public class AuthenticationTest {
private static final String TOKEN_HEADER = "X-Firebase-Auth";
final String url = "http://localhost:8080/authentication";
final Logger logger = LoggerFactory.getLogger(AuthenticationTest.class);
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
public Filter springSecurityFilterChain;
private MockMvc mockMvc;
#Before
public void setUp(){
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.addFilter(springSecurityFilterChain).build();
}
...
}
I based on this answer

Resources