Spring session with redis - how to mock it in integration tests? - spring

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

Related

Spring Boot Injecting Implementations for Prod and Test

I'm new to spring boot and I'm trying to wrap my head around how to make dependency injection work for deployment and testing.
I have a #RestController and a supporting #Service. The service injects another class that is an interface for talking to Kafka. For the Kafka interface I have two implementations: one real and one fake. The real one I want to use in production and the fake in test.
My approach is to use two different configuration for each environment (prod and test).
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
}
Then in my main application I would like to somehow load AppConfiguration.
#SpringBootApplication
public class DeployerServiceApiApplication {
public static void main(String[] args) {
SpringApplication.run(DeployerServiceApiApplication.class, args);
}
// TODO: somehow load here...
}
And in my test load the fake configuration somehow
#SpringBootTest
#AutoConfigureMockMvc(addFilters = false)
public class DeployerServiceApiApplicationTest {
#Autowired private MockMvc mockMvc;
// TODO: somehow load AppTestConfiguration here
#Test
public void testDeployAction() throws Exception {
...
ResultActions resultActions = mockMvc.perform(...);
...
}
}
I've spent the better part of a day trying to figure this out. What I'm trying to accomplish here is fundamental and should be straight forward yet I keep running into issues which makes me wonder if the way I'm thinking about this is all wrong.
Am not sure if i understand your question completely but from description i guess you wish to initialize bean based on environment. Please see below.
#Profile("test")
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Profile("prod")
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
and then you can pass the "-Dspring.profiles.active=prod" argument while starting you application using java command or you can also specify the profile in your test case like below.
#SpringBootTest
#ActiveProfile("test")
#AutoConfigureMockMvc(addFilters = false)
public class DeployerServiceApiApplicationTest
Use spring profiles, you can annotate your test class with #ActiveProfiles("test-kafka") and your test configuration with #Profile("test-kafka").
This is pretty easy task in spring boot world
Rewrite your classes as follows:
#Profile("test")
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Profile("prod")
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
}
This will instruct spring boot to load the relevant configuration when the "prod"/"test" specified.
Then you can start your application in production with --spring.profiles.active=prod and in the Test you can write something like this:
#SpringBootTest
#ActiveProfiles("test")
public class DeployerServiceApiApplicationTest {
...
}
If you want to run all the tests with this profile and do not want to write this ActiveProfiles annotation you can create src/test/resources/application.properties and put into it: spring.active.profiles=test

How to #Autowired a test controller invoked by WebTestClient in #WebFluxTest?

I have a Controller just for tests with dummy apis
#RestController
public class TestController {
#Autowired
private org.springframework.cloud.sleuth.Tracer tracer;
#GetMapping("/trace")
public Mono<String> traceTest() {
...
}
}
Here's my test
#WebFluxTest(controllers = TestController.class)
public MyTest {
#Autowired
private WebTestClient webClient;
#Test
public void testTrace() {
webClient.get().uri("/trace")...
}
}
When I try to run this, my Tracer is not Autowired.
Of course, if I change my test to a #SpringBootTest, it all works
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public MyTest {
...
}
But I'd like to avoid autowiring my entire spring application. How can I get TestController to be auto-configured?
The #WebFluxTest annotation populates a Spring TestContext with only a subset of the relevant beans:
#WebFluxTest auto-configures the Spring WebFlux infrastructure and
limits scanned beans to #Controller, #ControllerAdvice,
#JsonComponent, Converter, GenericConverter, WebFilter, and
WebFluxConfigurer. Regular #Component and #ConfigurationProperties
beans are not scanned when the #WebFluxTest annotation is used.
#EnableConfigurationProperties can be used to include
#ConfigurationProperties beans. from Spring Boot Documentation
Your Tracer won't be part of this TestContext out-of-the-box.
For your #WebFluxTest you can provide a mocked version of this bean with #MockBean:
#WebFluxTest(controllers = TestController.class)
public MyTest {
#Autowired
private WebTestClient webClient;
#MockBean
private Tracer tracer;
#Test
public void testTrace() {
webClient.get().uri("/trace")...
}
}
... and if you want to test the full integration I would rather use #SpringBootTest.

Is there a way to test nested objects without the web or persistence layer in Spring Boot?

I'm using JUnit5 to test a Spring Boot application. I want to test a #Service object, which uses #Autowired fields. I would like to mock another #Service object which is indirectly used by my test object. Concretely, I have the following (highly simplified) setup:
Object being tested:
#Service
public class MainService {
private #Autowired SubService subService;
public String test() {
return subService.test();
}
}
SubService:
#Service
public class SubService {
private #Autowired StringService stringService;
public String test() {
return stringService.test();
}
}
StringService:
#Service
public class StringService {
public String test() {
return "Real service";
}
}
Test class used:
#SpringBootTest
public class MainServiceTest {
private #Autowired MainService mainService;
private #MockBean StringService stringService;
#BeforeEach
public void mock() {
Mockito.when(stringService.test()).thenReturn("Mocked service");
}
#Test
public void test() {
assertEquals("Mocked service", mainService.test());
}
}
The above works if I run the test class as a #SpringBootTest, but this loads the full application and is very slow. I also want to avoid #WebMvcTest since I don't need the web server, or #DataJpaTest since I don't need persistence. I don't want to mock SubService, as it contains functionality I want to test together with the MainService.
I tried the following:
#ExtendWith(SpringExtension.class) => throws NoSuchBeanDefinitionException, it seems the autowiring does not work in this case
#ExtendWith(MockitoExtension.class) and using #InjectMocks and #Mock instead of the Spring annotations => as the StringService is not a direct field of the MainService being tested, this does not work.
Is there a way to use the spring dependency injection system without loading the web server or persistence layer, or alternatively not use Spring tests but allow for 'nested' dependency injection?
You can use profiling (i.e Spring #Profile) to avoid loading the whole application. It will look something like below:
#Profile("test")
#Configuration
public class TestConfiguration {
#Bean
public MainService mainService() {
return new MainService();
}
#Bean
public SubService subService() {
return new SubService();
}
// mock the StringService
#Bean
public StringService stringService() {
return Mockito.mock(StringService.class);
}
}
then use that profile with `#SpringBootTest(classes = TestConfiguration.class), it will look something like below:
#ActiveProfiles("test")
#SpringBootTest(classes = TestConfiguration.class)
class MainServiceTest {
#Autowired
private MainService mainService;
#Test
public void test() {
// configure behavior using apis like when(), basically however you
// want your mock to behave
}
}
This will load only the beans defined in the class TestConfiguration.
NOTE: Since your question is more about how to avoid loading the whole application, I've answered focusing on that. The above approach will get the job done, but I'd prefer constructor wiring over any other mode of dependency injection on any given day, it's easier to maintain and test(like cases where you want to mock).

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);
}
}

Do not want to load application.yml when testing with Spring Boot

How can I tell Spring to load only application-test.yml and not application.yml file ?
I have a config class:
#ActiveProfiles("test")
#SpringBootConfiguration
public class MongoTestConfig {
#Autowired
MongoOperations operations;
...
}
And a test class:
#RunWith(SpringRunner.class)
#DataMongoTest
#SpringBootTest(classes = MongoTestConfig.class)
public class TagDefinitionRepositoryTest {
...
#Test
....
}
I've tried to add :
#TestPropertySource(locations = {"classpath:application-test.yml"})
#ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
To my config class but it doesn't work: Spring still load application.yml
I don't think you can tell Spring Boot to ignore application.yml completely. What you can do though is to override all the non desired properties using test specific property files.
Based on the code snippet you posted, any property in application-test.yml will override the equivalent property in application.yml.
Spring Boot considers application-test.yml specific to the profile "test" (which has a higher priority over the default application.yml). No annotation #TestPropertySource is needed.
But if you want to choose another name for your properties file, then you can use #TestProertySource, since files indicated in #TestProperySource parameters have higher priority over the others.
You might want to have a look at Spring Boot external configuration rules for resolving properties
I've end up using #SpringBootTest instead of #DataMongoTest
#SpringBootConfiguration
#ComponentScan(basePackages = {"com.package.services"})
#EnableMongoRepositories(basePackages = {"com.package.repositories"})
public class MongoTestConfig {
private static final MongodStarter starter = MongodStarter.getDefaultInstance();
#Bean
public MongoClient mongoClient() throws IOException {
MongodExecutable _mongodExe;
MongodProcess _mongod;
_mongodExe = starter.prepare(new MongodConfigBuilder()
.version(Version.Main.V3_2)
.net(new Net("localhost", 12345, Network.localhostIsIPv6()))
.build());
_mongod = _mongodExe.start();
MongoClient _mongo = new MongoClient("localhost", 12345);
return _mongo;
}
#Bean
public MongoDbFactory mongoDbFactory() throws IOException{
return new SimpleMongoDbFactory(mongoClient() , "test");
}
#Bean
public MongoTemplate mongoTemplate() throws IOException {
return new MongoTemplate(mongoDbFactory());
}
And my test class is:
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyRepositoryTest {
...

Resources