How to control the #PostConstruct when integration testing using Spring - java

I have spring bean class RedisRepo
inside I am initialising my database connection with #PostConstruct:
#PostConstruct
public void init() {
logger.debug("RedisRepo, Init.");
client = new RedisClient(REDIS_HOST, REDIS_PORT);
...
}
I am creating this bean using java config at SpringConfiguration.class:
#Bean
#Scope("singleton")
public RedisRepo redisjRepo() {
return new RedisRepo();
}
I started to build Integration tests using Spring.
I am using the same configuration class (SpringConfiguration.class) for tests:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = SpringConfiguration.class)
My Test class using embedded-redis so I need to init it right before I start my tests:
#Before
public void init() throws IOException {
//init embedded-redis
}
The problem is when I start the tests the #PostConstruct of the RedisRepo class executed before my integration-test init() class (past below) which leading me to null since my embedded redis hasnt initialised just yet.
How could I avoid it?
Mybe I am not doing something right?
Thanks,
ray.

I would suggest to consider using spring Boot auto-configuration (#EnableAutoConfiguration or #SpringBootApplication) to initialize Redis connection. You can use these Spring Boot properties to customize Redis:
# REDIS (RedisProperties)
spring.redis.database= # database name
spring.redis.host=localhost # server host
spring.redis.password= # server password
spring.redis.port=6379 # connection port
spring.redis.pool.max-idle=8 # pool settings ...
spring.redis.pool.min-idle=0
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
spring.redis.sentinel.master= # name of Redis server
spring.redis.sentinel.nodes= # comma-separated list of host:port pairs
This would remove need for connection initialization in #PostConstruct production code and you can just autowire your Redis related beans from context dusting testing.
EDIT1:
To populate Redis instance before testing method you can use #BeforeMethod (using TestNg) or #Before (using JUnit). To populate it before the test but after the context is initialized use #PostConstruct in test class.
EDIT2:
You asked on generic rule how to overcome need for initialization of resources in #PostConstruct. I believe your problem is how you are wiring beans in your application.
Your #PostConstruct initialization is done in other bean, where RedisClient is stored as variable client. I would argue that it most probably mixing of concerns. If you register RedisClient bean into spring context this way:
#Bean
public RedisClient redisClient() {
RedisClient client = new RedisClient(REDIS_HOST, REDIS_PORT);
...
return client;
}
You can just autowire it into bean where you had #PostConstruct initialization. You are also able to autowire it during test. If RedisClient is not thread-safe, you may want to consider prototype or request scope for it.
With this approach, I extremely rarely use #PostConstruct and use Spring IoC container to handle all the reusable resource instances.

Related

#SpringBootTest requiring database connection?

I have some integration tests, for which I am using Testcontainers. But I have suddenly realized that when my docker container with database for my application is down, all the other tests (excluding the integration tests using Testcontainers) are failing (even the contextLoads() test generated by Spring Boot initializr)
I get:
java.lang.IllegalStateException: Failed to load ApplicationContext at
org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'liquibase' defined in class path
resource
[org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration$LiquibaseConfiguration.class]:
Invocation of init method failed; nested exception is
liquibase.exception.DatabaseException:
com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications
link failure
It is obvious that the application wants to connect to the database, and the database container is down.
I've been investigating, but I don't remember ever needing to start a container just for the test/build process of an application, so this problem is new for me. But if there is something done wrong, it could be here, in my AbstractDatabaseIT class:
#ActiveProfiles("test")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#ContextConfiguration(initializers = AbstractDatabaseIT.DockerMySqlDataSourceInitializer.class)
#Testcontainers
public abstract class AbstractDatabaseIT {
private static final String MYSQL_IMAGE_NAME = "mysql:5.7.24";
public static final MySQLContainer<?> mySQLContainer = new MySQLContainer<>(MYSQL_IMAGE_NAME);
static {
mySQLContainer.start();
}
public static class DockerMySqlDataSourceInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(#NotNull ConfigurableApplicationContext applicationContext) {
Map<String, String> parameters = new HashMap<>();
parameters.put("command", "--character-set-server=utf8");
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(
applicationContext,
"spring.datasource.url=" + mySQLContainer.getJdbcUrl(),
"spring.datasource.username=" + mySQLContainer.getUsername(),
"spring.datasource.password=" + mySQLContainer.getPassword()
);
mySQLContainer.setParameters(parameters);
}
}
}
The integration test extend this class:
public class ChallengeIT extends AbstractDatabaseIT {
#Autowired
private ChallengeRepository repository;
// tests here
All the other, non-integration classes have #SpringBootTest annotation, and the dependencies injected using #Autowired (maybe this is a problem here?)
#SpringBootTest
class EthMessageVerifierTest {
#Autowired
private EthMessageVerifier ethMessageVerifier;
// tests here
What am I missing here? I remember seeing the H2 database dependency all around many projects. Should I drop the testcontainers in favour of H2? Or can I somehow create a single testcontainer instance for all the other tests?
Tests that you annotate with #SpringBootTest try to populate the entire Spring context. This includes all your beans: your web layer, your business logic, your database setup, etc.
Hence all the infrastructure (e.g. messaging queues, remote systems, databases) that you need otherwise to run your entire application also needs to be present for such tests.
So #SpringBootTest also indicates an integration test and you need to provide your database setup as on application start, Spring Boot's auto-configuration tries to configure your DataSource.
For more information, consider this article on #SpringBootTest and this general overview about unit & integration testing with Spring Boot. You don't always have to use #SpringBootTest and can also use one of Spring Boots many test slice annotations to test something in isolation.

Error when calling configprops while spring batch jobscope is configured

I recently update my spring boot app 2.1.9 to 2.2.0 and i'm facing a problem. When i'm calling "configprops" from actuator endpoint, an exception is throw :
Scope 'job' is not active for the current thread
I reproduce the bug : https://github.com/guillaumeyan/bugspringbatch (just launch the test). Original project come from https://github.com/spring-guides/gs-batch-processing/tree/master/complete
I tried to add :
#Bean
public StepScope stepScope() {
final StepScope stepScope = new StepScope();
stepScope.setAutoProxy(true);
return stepScope;
}
but it does not work (with spring.main.allow-bean-definition-overriding=true)
Here is my configuration of the spring batch
#Bean
#JobScope
public RepositoryItemReader<DossierEntity> dossierToDiagnosticReader(PagingAndSortingRepository<DossierEntity, Long> dossierJpaRepository, #Value("#{jobParameters[origin]}") String origin) {
RepositoryItemReader<DossierEntity> diagnosticDossierReader = new RepositoryItemReader<>();
diagnosticDossierReader.setRepository(dossierJpaRepository);
diagnosticDossierReader.setMethodName("listForBatch");
// doing some stuff with origin
return diagnosticDossierReader;
}
ExceptionHandlerExceptionResolver[199] - Resolved [org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'scopedTarget.dossierToDiagnosticReader': Scope 'job' is not active for the current thread;
consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No context holder available for job scope]
I downloaded your project and was able to reproduce the case. There are two issues with your example:
You are defining a job scoped bean in your app but the JobScope is not defined in your context (and you are not using #EnableBatchProcessing annotation that adds it automatically to the context). If you want to use the job scope without #EnableBatchProcessing, you need to add it manually to the context.
Your test fails because there is no job running during your test. Job scoped beans are lazily instantiated when a job is actually run. Since your test does not start a job, the bean is not able to be proxied correctly.
Your test does not seem to test a batch job, I would exclude the job scoped bean from the test's context.
Bug resolve in spring boot 2.2.1 https://github.com/spring-projects/spring-boot/issues/18714

#Caching method called from spring boot test [annotated with #Transactional] not working

I'm using redis caching and spring boot annotations[#Cacheable and #CahePut],
I made RedisManager transactionAware, which will use the outer transaction[callee of caching layer]
#Bean
public RedisCacheManager cacheManager() {
RedisCacheManager rcm =
RedisCacheManager.builder(redisConnectionFactory())
.cacheDefaults(cacheConfiguration())
.transactionAware()
.build();
return rcm;
}
while testing as below, I'm using embedded redis-:
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureTestDatabase
#Transactional
public class RoleServiceImplTest extends TestingProfile {
#Before
public void setup() throws Exception {
//setup server and services
redisServer = new RedisServer(redisPort);
redisServer.start();
}
#Test
public void getUsersForRoleForTemplateRole() {
// call to caching layer methods directly annotated with #Cachable
}
...
Both times [ with and without #Transactional ] spring calls cache.put(key,result) without exception but it only persists values in case of without #Transactional.
Couldn't find much on internet, kudos to any help in advance.
In short just put #Commit or Rollback(false) annotation over your class or test method.
Spring by default rollback every Transaction after the test method.
https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#testcontext-tx
In the TestContext framework, transactions are managed by the TransactionalTestExecutionListener, which is configured by default, even if you do not explicitly declare #TestExecutionListeners on your test class. To enable support for transactions, however, you must configure a PlatformTransactionManager bean in the ApplicationContext that is loaded with #ContextConfiguration semantics (further details are provided later). In addition, you must declare Spring’s #Transactional annotation either at the class or the method level for your tests.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/context/transaction/TransactionalTestExecutionListener.html
Declarative Rollback and Commit Behavior
By default, test transactions will be automatically rolled back after completion of the test; however, transactional commit and rollback behavior can be configured declaratively via the #Commit and #Rollback annotations at the class level and at the method level.

How to set Spring Data Cassandra keyspace dynamically?

We're using Spring Boot 1.5.10 with Spring Data for Apache Cassandra and that's all working well.
We've had a new requirement coming along where we need to connect to a different keyspace while the service is up and running.
Through the use of Spring Cloud Config Server, we can easily set the value of spring.data.cassandra.keyspace-name, however, we're not certain if there's a way that we can dynamically switch (force) the service to use this new keyspace without having to restart if first?
Any ideas or suggestions?
Using #RefreshScope with properties/repositories doesn't work as the keyspace is bound to the Cassandra Session bean.
Using Spring Data Cassandra 1.5 with Spring Boot 1.5 you have at least two options:
Declare a #RefreshScope CassandraSessionFactoryBean, see also CassandraDataAutoConfiguration. This will interrupt all Cassandra operations upon refresh and re-create all dependant beans.
Listen to RefreshScopeRefreshedEvent and change the keyspace via USE my-new-keyspace;. This approach is less invasive and doesn't interrupt running queries. You'd basically use an event listener.
#Component
class MySessionRefresh {
private final Session session;
private final Environment environment;
// omitted constructors for brevity
#EventListener
#Order(Ordered.LOWEST_PRECEDENCE)
public void handle(RefreshScopeRefreshedEvent event) {
String keyspace = environment.getProperty("spring.data.cassandra.keyspace-name");
session.execute("USE " + keyspace + ";");
}
}
With Spring Data Cassandra 2, we introduced the SessionFactory abstraction providing AbstractRoutingSessionFactory for code-controlled routing of CQL/session calls.
Yes, you can use the #RefreshScope annotation on a the bean(s) holding the spring.data.cassandra.keyspace-name value.
After changing the config value through Spring Cloud Config Server, you have to issue a POST on the /refresh endpoint of your application.
From the Spring cloud documentation:
A Spring #Bean that is marked as #RefreshScope will get special treatment when there is a configuration change. This addresses the problem of stateful beans that only get their configuration injected when they are initialized. For instance if a DataSource has open connections when the database URL is changed via the Environment, we probably want the holders of those connections to be able to complete what they are doing. Then the next time someone borrows a connection from the pool he gets one with the new URL.
From the RefreshScope class javadoc:
A Scope implementation that allows for beans to be refreshed dynamically at runtime (see refresh(String) and refreshAll()). If a bean is refreshed then the next time the bean is accessed (i.e. a method is executed) a new instance is created. All lifecycle methods are applied to the bean instances, so any destruction callbacks that were registered in the bean factory are called when it is refreshed, and then the initialization callbacks are invoked as normal when the new instance is created. A new bean instance is created from the original bean definition, so any externalized content (property placeholders or expressions in string literals) is re-evaluated when it is created.

Resolving port already in use in a Spring boot test DEFINED PORT

I have a spring boot application that fires up and executes a class that listens to Application Ready event to call an external service to fetch some data and then use that data to push some rules to the classpath for execution. For local testing we have mocked the external service within our application which works fine during the application startup.
The issue is while testing the application by running it with spring boot test annotation and embedded jetty container either on :
RANDOM PORT
DEFINED PORT
In case of RANDOM PORT, at the application startup, it picks up the url for the mock service from the properties file at a defined port and has no clue where the embedded container is running since it is randomly picked up, hence failing to give response.
In case of DEFINED PORT, for the first test case file it runs successfully, but the moment next file is picked up, it fails saying the port is already in use.
The test cases are partitioned logically in multiple files and need
the external service to be called before the container starts to load
the rules.
How can I either share the embedded container between test files in case of using defined port or refactor my application code instead to get hold of the random port while starting up during the test case execution.
Any help would be appreciated.
Application Startup code :
#Component
public class ApplicationStartup implements ApplicationListener<ApplicationReadyEvent> {
#Autowired
private SomeService someService;
#Override
public void onApplicationEvent(ApplicationReadyEvent arg0) {
try {
someService.callExternalServiceAndLoadData();
}
catch (Execption e) {}
}
}
Test Code Annotations: Test1
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#TestPropertySource("classpath:test-application.properties")
public class Test1 {
#Autowired
private TestRestTemplate restTemplate;
#Test
public void tc1() throws IOException {.....}
Test Code Annotations: Test2
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#TestPropertySource("classpath:test-application.properties")
public class Test2 {
#Autowired
private TestRestTemplate restTemplate;
#Test
public void tc1() throws IOException {.....}
If you insist on using the same port on multiple test, you can prevent spring from caching the context for further tests by annotating your testclass with: #DirtiesContext
In your case:
#RunWith(SpringRunner.class)
#DirtiesContext
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#TestPropertySource("classpath:test-application.properties")
Here is a quote from Andy Wilkinson from his answer on this discussion
This is working as designed. Spring Framework's test framework will, by default, cache contexts for possible reuse by multiple test classes. You have two tests with different configuration (due to #TestPropertySource) so they will use different application contexts. The context for the first test will be cached and kept open while the second test is running. Both tests are configured to use the same port for Tomcat's connector. As a result, when the second test is run, the context fails to start due to a port clash with the connector from the first test. You have a few options:
Use RANDOM_PORT
Remove #TestPropertySource from Test2 so that the contexts have identical configuration and the context from the first test can be reused for the second test.
Use #DirtiesContext so that the context isn't cached
I ran across the same issue. I know this question is a little old, but this may be of assistance:
Tests that use #SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) can also inject the actual port into a field by using the #LocalServerPort annotation, as shown in the following example:
Source: https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#howto-user-a-random-unassigned-http-port
The code example given is:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
public class MyWebIntegrationTests {
#Autowired
ServletWebServerApplicationContext server;
#LocalServerPort
int port;
// ...
}
in application.properties
server.port=0
will run the application in random ports

Categories