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);
}
}
Related
I'm new in Spring Boot AOP.
Does an AOP method annotated with #Before run before java validation annotations (such as #NotNull)?
I have some other custom validations that need to run for every request but I need to run these validations after java validation annotations run.
Which one will run first?
my Controller:
#RestController
#RequestMapping("/users")
public class UserController {
private final UserService userService;
#Autowired
public UserController(UserService userService) {
this.userService = userService;
}
#PostMapping(value = "")
public List<User> getAllUsers(#Valid #RequestBody User user) {
return userService.getAllUsers();
}
}
and my advice:
#Aspect
#Component
public class AspectConfig {
#Pointcut(value = "within(#org.springframework.web.bind.annotation.RestController *)")
public void restControllers() {
}
#Before(value = "restControllers()")
public void logRequest(JoinPoint joinPoint) {
...
}
}
Does an AOP method annotated with #Before run before java validation annotations
No, it runs afterwards, just like you wish. See also this question. So you should be all set. Your logging advice should only be triggered if validation was successful, because only then the target method will be called.
You can implement a HandlerInterceptor if you wish to log/do something on the request level before validators kick in, see here.
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.
I am trying to write some test using WebTestClient under Springboot 2.1.8 and Junit5
It's always returning < 401 UNAUTHORIZED Unauthorized, but actually it didn't go to the controller or service layer at all. It may related to spring security, just my guess.
The project was generated using JHipster. Here is the build.gradle
-----------------UimApiServiceImplTest.java-------------------
...
#ExtendWith(SpringExtension.class)
#WebFluxTest(controllers = UserGuidController.class)
#ContextConfiguration(classes = {UserGuidController.class, UimApiServiceImpl.class})
public class UimApiServiceImplTest {
#Autowired
private WebTestClient webTestClient;
#Test
public void testGetGuidByEmail() {
webTestClient.get()
.uri("/uimapi/getguid/{email}", "someone#xxxxx.com")
.accept(MediaType.APPLICATION_JSON_UTF8)
.exchange()
.expectStatus().isOk();
}
}
--------------------UserGuidController.java--------------------
...
#RestController
#RequestMapping("/uimapi")
public class UserGuidController {
#Autowired
private UimApiServiceImpl uimApiService;
private static final Logger logger = LoggerFactory.getLogger(UserGuidController.class);
#GetMapping("/getguid/{email}")
public String getUserGuid(#PathVariable String email) {
return uimApiService.getUserGuid(email);
}
}
For webflux, you can disable SecurityAutoconfiguration by excluding the ReactiveSecurityAutoConfiguration class like this:
#WebFluxTest(controllers = YourController.class,excludeAutoConfiguration = {ReactiveSecurityAutoConfiguration.class}))
You've implementation "org.springframework.boot:spring-boot-starter-security" in your gradle dependencies. Spring boot automatically enables security on all endpoints by default when this dependency is found in classpath.
You can choose to disable this default configuration by updating your main class:
#SpringBootApplication(exclude = { SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class })
This would disable security on all endpoints.
If you want to have control of which endpoints to remove security from, you can do that using the below code with updates matching your requirements:
#Configuration
public class SecurityConfiguration {
#Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange().anyExchange().permitAll();
return http.build();
}
}
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
So, Im using spring session in my project.
How to write integration tests in project using it? Should I mock something for spring session internals? Or any reasonable way to use embedded redis?
I saw there was some #EnableEmbeddedRedis annotation in past, but seems it was removed: https://github.com/spring-projects/spring-session/issues/248
//edit
Ive tried to pass MockHttpSession to
mockMvc.perform(post("/register").session(mockHttpSession)
but spring tries and fails to connect to redis anyway.
You can create your own connectionfactory and the redisserializer. Then spring boot won't create their default beans.
Example:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
public class ApplicationTest
{
#Test
public void contextLoads()
{
}
#EnableRedisHttpSession
#Configuration
static class Config
{
#Bean
#SuppressWarnings("unchecked")
public RedisSerializer<Object> defaultRedisSerializer()
{
return Mockito.mock(RedisSerializer.class);
}
#Bean
public RedisConnectionFactory connectionFactory()
{
RedisConnectionFactory factory = Mockito.mock(RedisConnectionFactory.class);
RedisConnection connection = Mockito.mock(RedisConnection.class);
Mockito.when(factory.getConnection()).thenReturn(connection);
return factory;
}
}
}
I would not suggest to use mockHttpSession, as it will bypass the integration with spring-session library in the tests. I would
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class ExampleControllerV2SpringSessionTest {
#Autowired
private WebApplicationContext wac;
#Autowired
private SessionRepository sessionRepository;
#Autowired
private SessionRepositoryFilter sessionRepositoryFilter;
//this is needed to test spring-session specific features
private MockMvc mockMvcWithSpringSession;
#Before
public void setup() throws URISyntaxException {
this.mockMvcWithSpringSession = MockMvcBuilders
.webAppContextSetup(wac)
.addFilter(sessionRepositoryFilter)
.build();
}
}
Knowing that it's hard to always have redis instance ready during the test, I would suggest you to use this property spring.session.store-type=hash_map for your test cases.
ok, ive just disabled redis by using profiles in my integration tests
#ActiveProfiles("integrationtests")
class RegisterControllerTest extends Specification {...
and extracting #EnableRedisHttpSession to its own class:
#Configuration
#EnableRedisHttpSession
#Profile("!integrationtests")
public class RedisConfig {
}
Its more like workaround than solution, but I dont need to test anything inside session anyway.
This work:
In your HttpSessionConfig define profiles where the configuration is active:
#EnableRedisHttpSession
#Profile("prod")
class HttpSessionConfig {
}
In application-prod.properties add:
#REDIS SERVER
spring.redis.host=xxxxxxxx
And then in your application.properties of test add:
#REDIS SERVER
spring.data.redis.repositories.enabled=false
spring.session.store-type=none