SpringBootTest, Testcontainers, container start up - Mapped port can only be obtained after the container is started - spring-boot

I am using docker/testcontainers to run a postgresql db for testing. I have effectively done this for unit testing that is just testing the database access. However, I have now brought springboot testing into the mix so I can test with an embedded web service and I am having problems.
The issue seems to be that the dataSource bean is being requested before the container starts.
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [com/myproject/integrationtests/IntegrationDataService.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.sql.DataSource]: Factory method 'dataSource' threw exception; nested exception is java.lang.IllegalStateException: Mapped port can only be obtained after the container is started
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.sql.DataSource]: Factory method 'dataSource' threw exception; nested exception is java.lang.IllegalStateException: Mapped port can only be obtained after the container is started
Caused by: java.lang.IllegalStateException: Mapped port can only be obtained after the container is started
Here is my SpringBootTest:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = {IntegrationDataService.class, TestApplication.class},
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringBootTestControllerTesterIT
{
#Autowired
private MyController myController;
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Test
public void testRestControllerHello()
{
String url = "http://localhost:" + port + "/mycontroller/hello";
ResponseEntity<String> result =
restTemplate.getForEntity(url, String.class);
assertEquals(result.getStatusCode(), HttpStatus.OK);
assertEquals(result.getBody(), "hello");
}
}
Here is my spring boot application referenced from the test:
#SpringBootApplication
public class TestApplication
{
public static void main(String[] args)
{
SpringApplication.run(TestApplication.class, args);
}
}
Here is the IntegrationDataService class which is intended to startup the container and provide the sessionfactory/datasource for everything else
#Testcontainers
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#EnableTransactionManagement
#Configuration
public class IntegrationDataService
{
#Container
public static PostgreSQLContainer postgreSQLContainer = (PostgreSQLContainer) new PostgreSQLContainer("postgres:9.6")
.withDatabaseName("test")
.withUsername("sa")
.withPassword("sa")
.withInitScript("db/postgresql/schema.sql");
#Bean
public Properties hibernateProperties()
{
Properties hibernateProp = new Properties();
hibernateProp.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
hibernateProp.put("hibernate.format_sql", true);
hibernateProp.put("hibernate.use_sql_comments", true);
// hibernateProp.put("hibernate.show_sql", true);
hibernateProp.put("hibernate.max_fetch_depth", 3);
hibernateProp.put("hibernate.jdbc.batch_size", 10);
hibernateProp.put("hibernate.jdbc.fetch_size", 50);
hibernateProp.put("hibernate.id.new_generator_mappings", false);
// hibernateProp.put("hibernate.hbm2ddl.auto", "create-drop");
// hibernateProp.put("hibernate.jdbc.lob.non_contextual_creation", true);
return hibernateProp;
}
#Bean
public SessionFactory sessionFactory() throws IOException
{
LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource());
sessionFactoryBean.setHibernateProperties(hibernateProperties());
sessionFactoryBean.setPackagesToScan("com.myproject.model.entities");
sessionFactoryBean.afterPropertiesSet();
return sessionFactoryBean.getObject();
}
#Bean
public PlatformTransactionManager transactionManager() throws IOException
{
return new HibernateTransactionManager(sessionFactory());
}
#Bean
public DataSource dataSource()
{
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(postgreSQLContainer.getDriverClassName());
dataSource.setUrl(postgreSQLContainer.getJdbcUrl());
dataSource.setUsername(postgreSQLContainer.getUsername());
dataSource.setPassword(postgreSQLContainer.getPassword());
return dataSource;
}
}
The error occurs on requesting the datasource bean from the sessionFactory from one of the Dao classes before the container starts up.
What the heck am I doing wrong?
Thanks!!!

The reason for your java.lang.IllegalStateException: Mapped port can only be obtained after the container is started exception is that when the Spring Context now gets created during your test with #SpringBootTest it tries to connect to the database on application startup.
As you only launch your PostgreSQL inside your IntegrationDataService class, there is a timing issue as you can't obtain the JDBC URL or create a connection on application startup as this bean is not yet properly created.
In general, you should NOT use any test-related code inside your IntegrationDataService class. Starting/stopping the database should be done inside your test setup.
This ensures to first start the database container, wait until it's up- and running, and only then launch the actual test and create the Spring Context.
I've summarized the required setup mechanism for JUnit 4/5 with Testcontainers and Spring Boot, that help you get the setup right.
In the end, this can look like the following
// JUnit 5 example with Spring Boot >= 2.2.6
#Testcontainers
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ApplicationIT {
#Container
public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer()
.withPassword("inmemory")
.withUsername("inmemory");
#DynamicPropertySource
static void postgresqlProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
}
#Test
public void contextLoads() {
}
}

Related

Spring Batch test case with multiple data sources

I have a Spring Batch Classifier to test for which I've defined this test class:
#RunWith(SpringRunner.class)
#SpringBatchTest
#ContextConfiguration(classes = { BatchConfiguration.class })
class CsvOutputClassifierTest {
#Autowired
private FlatFileItemWriter<CsvData> createRequestForProposalWriter;
#Autowired
private FlatFileItemWriter<CsvData> createRequestForQuotationWriter;
private final CsvOutputClassifier csvOutputClassifier = new CsvOutputClassifier(
createRequestForProposalWriter,
createRequestForQuotationWriter);
#Test
void shouldReturnProposalWriter() {
...
}
The batch configuration class has this constructor:
public BatchConfiguration(
final JobBuilderFactory jobBuilderFactory,
final StepBuilderFactory stepBuilderFactory,
#Qualifier("oerationalDataSource") final DataSource oerationalDataSource,
final DwhFileManager dwhFileManager,
final OperationalRepository operationalRepository)
And these beans:
#StepScope
#Bean
public FlatFileItemWriter<CsvData> createRequestForProposalWriter(
#Value("#{jobParameters['startDate']}") String startDate) {
FlatFileItemWriter<CsvData> writer = new FlatFileItemWriter<CsvData>();
...
return writer;
}
#StepScope
#Bean
public FlatFileItemWriter<CsvData> createRequestForQuotationWriter(
#Value("#{jobParameters['startDate']}") String startDate) {
FlatFileItemWriter<CsvData> writer = new FlatFileItemWriter<CsvData>();
...
return writer;
}
Running the test class I'm not able to trigger the first test method as I'm getting:
Error creating bean with name 'batchConfiguration': Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'javax.sql.DataSource' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Qualifier(value="oerationalDataSource")}
In fact, I defined two different data sources, one for the 'operational' data and the 'app' for Spring Batch persistency:
#Configuration(proxyBeanMethods = false)
public class DataSourceConfiguration {
#Bean
#Primary
#ConfigurationProperties("app.datasource")
public DataSourceProperties defaultDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
#ConfigurationProperties("app.datasource.configuration")
public HikariDataSource defaultDataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().type(HikariDataSource.class)
.build();
}
#Bean
#ConfigurationProperties("aggr.datasource")
public DataSourceProperties oerationalDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("aggr.datasource.configuration")
public HikariDataSource oerationalDataSource(
#Qualifier("oerationalDataSourceProperties") DataSourceProperties oerationalDataSourceProperties) {
return oerationalDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
#Bean
public JdbcTemplate operationalJdbcTemplate(#Qualifier("oerationalDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
In #SpringBatchTest documentation it is reported that just one DataSource should be found or it should be marked as Primary:
It should be noted that JobLauncherTestUtils requires a org.springframework.batch.core.Job bean and that JobRepositoryTestUtils requires a javax.sql.DataSource bean. Since this annotation registers a JobLauncherTestUtils and a JobRepositoryTestUtils in the test context, it is expected that the test context contains a single autowire candidate for a org.springframework.batch.core.Job and a javax.sql.DataSource (either a single bean definition or one that is annotated with org.springframework.context.annotation.Primary).
But I have it. So how to fix it?
Update #1
Thanks to #Henning's tip, I've changed the annotations as follows:
#RunWith(SpringRunner.class)
#SpringBatchTest
#SpringBootTest(args={"--mode=custom", "--startDate=2022-05-31T01:00:00.000Z", "--endDate=2022-05-31T23:59:59.999Z"})
#ContextConfiguration(classes = { BatchConfiguration.class, DataSourceConfiguration.class, LocalFileManager.class, AggregatorRepository.class })
#ActiveProfiles({"integration"})
#EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
Where:
#SpringBootTest is needed to avoid 'Failed to determine a suitable driver class exception'
args is needed to provide the required parameters to the batch
But still having this exception:
Error creating bean with name 'scopedTarget.createRequestForProposalWriter' defined in BatchConfiguration: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.batch.item.file.FlatFileItemWriter]: Factory method 'createRequestForProposalWriter' threw exception; nested exception is java.lang.NullPointerException: text
raised in the implementation of:
#StepScope
#Bean
public FlatFileItemWriter<CsvData> createRequestForProposalWriter(
#Value("#{jobParameters['startDate']}") String startDate)
as the parameter 'startDate' is null.
In my naivety I assumed that I could test in isolation the classifier with something like that:
#Test
void shouldReturnProposalWriter() {
CsvData csvData = create-some-fake-data
CsvOutputClassifier csvOutputClassifier = new CsvOutputClassifier(
createRequestForProposalWriter,
createRequestForQuotationWriter);
ItemWriter itemWriter = csvOutputClassifier.classify(csvData);
some-assert-about-itemWriter-properties
}
So now the question is: how to correctly test the classifier?
You need to list DataSourceConfiguration as argument of #ContextConfiguration, i.e. your test class should start like this
#RunWith(SpringRunner.class)
#SpringBatchTest
#ContextConfiguration(classes = { BatchConfiguration.class, DataSourceConfiguration.class })
class CsvOutputClassifierTest {
...
}
The DataSourceConfiguration is currently not known within the test as you didn't declare it as part of the context or enabled classpath scanning in any form.

How to test complete IntegrationFlow from MessagingGateway to ServiceActivator

In our Spring Boot project we have the following IntegrationFlow configuration
package our.configs;
#Configuration
#EnableIntegration
public class MessageChannelsConfiguration {
public static final String OUTBOUND_CHANNEL = "outboundChannel";
public static final String OUTBOUND_CHANNEL_GROUP_ID = "outboundMessageGroup";
#Bean
IntegrationFlow outboundSnapshotMessageChannel(ChannelMessageStore outboundChannelMessageStore,
OutboundFixMessageService outboundMessageService) {
return f -> f
.channel(c -> c.queue(
OUTBOUND_CHANNEL,
outboundChannelMessageStore,
OUTBOUND_CHANNEL_GROUP_ID))
.handle(outboundMessageService, "processOutboundMessage");
}
#Bean
OutboundMessageService outboundFixMessageService(ObjectMapper objectMapper){
return new OutboundMessageService(objectMapper);
}
#Bean
ChannelMessageStore outboundChannelMessageStore(#Qualifier("dataSource") DataSource dataSource,
ChannelMessageStoreQueryProvider channelMessageStoreQueryProvider) {
JdbcChannelMessageStore jdbcChannelMessageStore = new JdbcChannelMessageStore(dataSource);
jdbcChannelMessageStore.setChannelMessageStoreQueryProvider(channelMessageStoreQueryProvider);
jdbcChannelMessageStore.setRegion("TX_TIMEOUT");
return jdbcChannelMessageStore;
}
#Bean
#Profile({"test"})
ChannelMessageStoreQueryProvider jdbcChannelMessageStoreQueryProvider() {
return new H2ChannelMessageStoreQueryProvider();
}
#Bean(name = PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller(TransactionManager transactionManager,
Initiator clientInitiator) {
return Pollers.fixedRate(500))
.maxMessagesPerPoll(1)
.advice(transactionInterceptor(transactionManager), new CheckSessionPollingAdvise(clientInitiator))
.get();
}
private TransactionInterceptor transactionInterceptor(TransactionManager transactionManager) {
return new TransactionInterceptorBuilder()
.transactionManager(transactionManager)
.propagation(Propagation.NESTED)
.build();
}
}
and the messaging Gateway which is defined as in a separate package then the above configuration
package our.businesslogic;
#MessagingGateway
public interface OutboundMessageGateway {
#Gateway(requestChannel = MessageChannelsConfiguration.OUTBOUND_CHANNEL)
void sendMarkerMessage(Object markerMessage,
#Header(ChannelMessageHeaders.RECIPIENT_ID) String institutionId,
#Header(ChannelMessageHeaders.MESSAGE_TYPE) ChannelMessageType channelMessageType);
#Gateway(requestChannel = MessageChannelsConfiguration.OUTBOUND_CHANNEL)
void sendOrderMessage(Order message,
#Header(ChannelMessageHeaders.RECIPIENT_ID) String institutionId,
#Header(ChannelMessageHeaders.MESSAGE_TYPE) ChannelMessageType channelMessageType);
}
I want to test the behavior of the complete flow including the persistence to the JdbcChannelMessageStore (and later also the transactional scenarios) with JUnit5.
E.g.
#Test
void whenSendTrancheMessage_givenPollingBlockedByAdvise_thenCorrectNumberOfMessagesOnQueue() {
//given
String recipientId = "Mocked-recipient";
List<Order> orders = Arrays.asList(
new Order(),
new Order(),
new Order(),
new Order()
);
//when
clientInitiator.stopConnection(); // Queue will not be read as long as
// there is no connection
orders.forEach(order->
outboundMessageGateway.sendOrderMessage(order,recipientId,ChannelMessageType.SNAPSHOT));
//then
Assertions.assertThat(outboundChannelMessageStore.messageGroupSize(FixMessageChannelsConfiguration.OUTBOUND_CHANNEL_GROUP_ID))
.isEqualTo(orders.size());
}
#Test
void whenSendTrancheMessage_givenPollingIsNotBlocked_thenMessagesAreReceivedByHandler() {
//some test code with mocked ServiceActivator
}
I have tried with two different ways
as an Integration test with #SpringBootTest
as a context specific JUnit test with #ContextConfiguration and #SpringIntegrationTest
In case of 1) my tests are working when called separately, but are failing with the following exception when they are run together with existing integration tests
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springSecurityFilterChain' defined in class p
ath resource [org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]: Bean instantiation vi
a factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servl
et.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is org.h2.jdbc.JdbcSQLNonTransientConnection
Exception: Exception opening port "9092" (port may be in use), cause: "java.net.BindException: Address already in use: JVM_Bind" [900
61-199]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:655)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:483)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowir
eCapableBeanFactory.java:1336)
In case of 2) the following exception is thrown showing problems with the outboundMessageGateway
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 'outboundMessageGateway': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [our.businesslogic.OutboundMessageGateway]: Specified class is an interface
I would appreciate it very much if someone could help me to solve this issues.
Exception opening port "9092"
You probably need to add a #DirtiesContext along side with the mentioned #SpringBootTest.
Failed to instantiate [our.businesslogic.OutboundMessageGateway]
If you don't use Spring Boot in your test environment, you must be explicit #EnableIntegration and #IntegrationComponentScan must be there to Spring Integration infrastructure available.
See docs for more info: https://docs.spring.io/spring-integration/docs/current/reference/html/overview.html#configuration-enable-integration
Assertions.assertThat(outboundChannelMessageStore.messageGroupSize(FixMessageChannelsConfiguration.OUTBOUND_CHANNEL_GROUP_ID))
.isEqualTo(orders.size());
You can't check this if your handle(outboundMessageService, "processOutboundMessage") is not stopped. Since you have a poller for the queue channel, it is going to pull all the message from the store and lets that handler to process them. So, there is going to be nothing in the store at the moment you try to verify it (or some wrong unexpected number).

Want to configure Job with out DataSource

I am able to run my Springbatch application when Datasource was configured in my SpringbatchConfiguration class. But i dont want Datasource to be configured. SO i used ResourcelessTransactionManager. See below my configuration class. Some one guide me how i can launch Jobs without configuring Datasource as part of Batchjob configurations.
#Configuration
#EnableBatchProcessing
#EnableAutoConfiguration
//#EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class SprintgBatchConfiguration {
/*#Autowired
private DBConfiguration dbConfig;*/
/*#Autowired
private DataSource dataSource;
#Autowired
private DataSourceTransactionManager transactionManager;
*/
//Tomcat relaated configuration//
#Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxFileSize("124MB");
factory.setMaxRequestSize("124MB");
return factory.createMultipartConfig();
}
#Bean(name="csvjob")
public Job job(JobBuilderFactory jobBuilderFactory,StepBuilderFactory stepBuilderFactory,ItemReader<List<CSVPojo>> itemReader,ItemProcessor<List<CSVPojo>,CsvWrapperPojo> itemProcessor,AmqpItemWriter<CsvWrapperPojo> itemWriter){
Step step=stepBuilderFactory.get("ETL-CSV").<List<CSVPojo>,CsvWrapperPojo>chunk(100)
.reader(itemReader)
.processor(itemProcessor)
.writer(itemWriter)
.build();
Job csvJob= jobBuilderFactory.get("ETL").incrementer(new RunIdIncrementer())
.start(step).build();
return csvJob;
}
#Bean(name="exceljob")
public Job jobExcel(JobBuilderFactory jobBuilderFactory,StepBuilderFactory stepBuilderFactory,ItemReader<List<ExcelPojo>> itemReader,ItemProcessor<List<ExcelPojo>,ExcelWrapperPojo> itemProcessor,AmqpItemWriter<ExcelWrapperPojo> itemWriter){
Step step=stepBuilderFactory.get("ETL-Excel").<List<ExcelPojo>,ExcelWrapperPojo>chunk(100)
.reader(itemReader)
.processor(itemProcessor)
.writer(itemWriter)
.build();
Job ExcelJob= jobBuilderFactory.get("ETL-Excel").incrementer(new RunIdIncrementer())
.start(step).build();
return ExcelJob;
}
/*#Override
public void setDataSource(DataSource dataSource){
System.out.println("overriden");
}*/
/*#Bean
public FlatFileItemReader<CSVPojo> fileItemReader(Resource resource){
return null;
}*/
/*#Bean(name="dataSource")
public DataSource dataSource() throws SQLException
{
//BasicDataSource dataSource = new BasicDataSource();
return dataSource;
}*/
#Bean(name="transactionManager")
public ResourcelessTransactionManager transactionManager() throws SQLException{
return new ResourcelessTransactionManager();
}
/*#Bean(name="transactionManager")
public DataSourceTransactionManager transactionManager() throws SQLException{
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(this.dataSource());
return transactionManager;
}*/
/*#Bean
public JobRepository jobRepository() throws Exception{
JobRepositoryFactoryBean factoryBean = new JobRepositoryFactoryBean();
factoryBean.setDatabaseType("ORACLE");
factoryBean.setDataSource(dataSource);
factoryBean.setTransactionManager(transactionManager);
factoryBean.setIsolationLevelForCreate("ISOLATION_READ_UNCOMMITTED");
return factoryBean.getObject();
}*/
#Bean
public JobRepository jobRepository(ResourcelessTransactionManager transactionManager) throws Exception {
MapJobRepositoryFactoryBean mapJobRepositoryFactoryBean = new MapJobRepositoryFactoryBean(transactionManager);
mapJobRepositoryFactoryBean.setTransactionManager(transactionManager);
return mapJobRepositoryFactoryBean.getObject();
}
}
But i am getting below exception when i am running Application.
ationConfigEmbeddedWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'fileProcessController': Unsatisfied dependency expressed through field 'jobLauncher'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration': Unsatisfied dependency expressed through field 'dataSources'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Tomcat.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.tomcat.jdbc.pool.DataSource]: Factory method 'dataSource' threw exception; nested exception is org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Cannot determine embedded database driver class for database type NONE. If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).
Thanks in advance!!!!
Spring Boot is intended for building production grade applications. When it is used to build a Spring Batch application, it requires a data source to persist Spring Batch meta-data (See BATCH-2704).
But you can always use either:
an embedded datasource supported by Spring Boot (H2, HSQL or Derby) by just adding it to the classpath. This data source will be picked up automatically by Spring Batch
or provide a custom BatchConfigurer and use the MapJobRepository (See here)
Hope this helps.

When run spring boot tests got hazelcast.core.DuplicateInstanceNameException

How to execute integration tests of spring boot application with using Hazelcast, because when run all tests got hazelcast.core.DuplicateInstanceNameException?
I use Spring Boot 2.0.0.RC1 and Hazelcast 3.9.2
Use java configuration for hazelcast:
#Bean
public Config getHazelCastServerConfig() {
final Config config = new Config();
config.setInstanceName(hzInstance);
config.getGroupConfig().setName(hzGroupName).setPassword(hzGroupPassword);
final ManagementCenterConfig managementCenterConfig = config.getManagementCenterConfig();
managementCenterConfig.setEnabled(true);
managementCenterConfig.setUrl(mancenter);
final MapConfig mapConfig = new MapConfig();
mapConfig.setName(mapName);
mapConfig.setEvictionPolicy(EvictionPolicy.NONE);
mapConfig.setTimeToLiveSeconds(0);
mapConfig.setMaxIdleSeconds(0);
config.getScheduledExecutorConfig(scheduler)
.setPoolSize(16)
.setCapacity(100)
.setDurability(1);
final NetworkConfig networkConfig = config.getNetworkConfig();
networkConfig.setPort(5701);
networkConfig.setPortAutoIncrement(true).setPortCount(30);
final JoinConfig joinConfig = networkConfig.getJoin();
joinConfig.getMulticastConfig().setEnabled(false);
joinConfig.getAwsConfig().setEnabled(false);
final TcpIpConfig tcpIpConfig = joinConfig.getTcpIpConfig();
tcpIpConfig.addMember(memberOne)
.addMember(memberTwo);
tcpIpConfig.setEnabled(true);
return config;
}
#Bean
public HazelcastInstance getHazelCastServerInstance() {
final HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(getHazelCastServerConfig());
hazelcastInstance.getClientService().addClientListener(new ClientListener() {
#Override
public void clientConnected(Client client) {
System.out.println(String.format("Connected %s %s %s", client.getClientType(), client.getUuid(), client.getSocketAddress()));
log.info(String.format("Connected %s %s %s", client.getClientType(), client.getUuid(), client.getSocketAddress()));
}
#Override
public void clientDisconnected(Client client) {
System.out.println(String.format("Disconnected %s %s %s", client.getClientType(), client.getUuid(), client.getSocketAddress()));
log.info(String.format("Disconnected %s %s %s", client.getClientType(), client.getUuid(), client.getSocketAddress()));
}
});
return hazelcastInstance;
}
I have simple test:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = UpaSdcApplication.class)
#ActiveProfiles("test")
public class CheckEndpoints {
#Autowired
private ApplicationContext context;
private static final String HEALTH_ENDPOINT = "/actuator/health";
private static WebTestClient testClient;
#Before
public void init() {
testClient = org.springframework.test.web.reactive.server.WebTestClient
.bindToApplicationContext(context)
.configureClient()
.filter(basicAuthentication())
.build();
}
#Test
public void testHealth(){
testClient.get().uri(HEALTH_ENDPOINT).accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus()
.isOk()
.expectBody()
.json("{\"status\": \"UP\"}");
}
}
If run with test class separate from other tests - it execute fine and passes.
If run wiith other tests - get exception:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.hazelcast.core.HazelcastInstance]: Factory method 'getHazelCastServerInstance' threw exception; nested exception is com.hazelcast.core.DuplicateInstanceNameException: HazelcastInstance with name 'counter-instance' already exists!
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:579)
... 91 more
Caused by: com.hazelcast.core.DuplicateInstanceNameException: HazelcastInstance with name 'counter-instance' already exists!
at com.hazelcast.instance.HazelcastInstanceFactory.newHazelcastInstance(HazelcastInstanceFactory.java:170)
at com.hazelcast.instance.HazelcastInstanceFactory.newHazelcastInstance(HazelcastInstanceFactory.java:124)
at com.hazelcast.core.Hazelcast.newHazelcastInstance(Hazelcast.java:58)
at net.kyivstar.upa.sdc.config.HazelcastConfiguration.getHazelCastServerInstance(HazelcastConfiguration.java:84)
at net.kyivstar.upa.sdc.config.HazelcastConfiguration$$EnhancerBySpringCGLIB$$c7da65f3.CGLIB$getHazelCastServerInstance$0(<generated>)
at net.kyivstar.upa.sdc.config.HazelcastConfiguration$$EnhancerBySpringCGLIB$$c7da65f3$$FastClassBySpringCGLIB$$b920d5ef.invoke(<generated>)
How do you solve this problem? How do you run integration tests?
I had the same problem and I solved it checking if the instance already exists or not:
#Bean
public CacheManager cacheManager() {
HazelcastInstance existingInstance = Hazelcast.getHazelcastInstanceByName(CACHE_INSTANCE_NAME);
HazelcastInstance hazelcastInstance = null != existingInstance
? existingInstance
: Hazelcast.newHazelcastInstance(hazelCastConfig());
return new HazelcastCacheManager(hazelcastInstance);
}
You can see the rest of the code here.
instanceName configuration element is used to create a named Hazelcast member and should be unique for each Hazelcast instance in a JVM. In your case, either you should set a different instance name for each HazelcastInstance bean creation in the same JVM, or you can totally remove instanceName configuration if you don't recall instances by using instance name.
Had the same issue while running my tests. In my case reason was,that spring test framework was trying to launch new context, while keeping old one cached - thus trying to create another hazelcast instance with the same name, while one was already in the cached context.
Once the TestContext framework loads an ApplicationContext (or
WebApplicationContext) for a test, that context is cached and reused
for all subsequent tests that declare the same unique context
configuration within the same test suite.
Read here to understand more about how spring test framework manages test context.
I am working at the solution at the moment, will post it later. One possible solution I can see is droping test context after each test with #DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS), but this is very expensive in terms of performance.

Cucumber Spring and class configuration

I'm struggling with Cucumber and Spring configuration.
I'm writing selenium framework using Page Object Pattern, with BrowserFactory.
When I use #ComponentScan, #Component and #Autowire annotations everything works fine, but when I want to create a bit more complicated bean with #Bean annotation (BrowserFactory which registers few browser drivers) in #Configuration class it does not work, during debug I'm getting nulls on every single variable I'm trying to Autowire.
I'm using Spring 4.2.4, all cucumber dependencies in version 1.2.4.
Config:
#Configuration
public class AppConfig {
#Bean
#Scope("cucumber-glue")
public BrowserFactory browserFactory() {
BrowserFactory browserFactory = new BrowserFactory();
browserFactory.registerBrowser(new ChromeBrowser());
browserFactory.registerBrowser(new FirefoxBrowser());
return browserFactory;
}
#Bean(name = "loginPage")
#Scope("cucumber-glue")
public LoginPage loginPage() throws Exception {
return new LoginPage();
}
#Bean(name = "login")
#Scope("cucumber-glue")
public Login login() {
return new Login();
}
}
POP:
public class LoginPage extends Page {
public LoginPage() throws Exception {
super();
}
...
}
Page:
public class Page {
#Autowired
private BrowserFactory browserFactory;
public Page() throws Exception{
...
}
}
Login:
public class Login {
#Autowired
private LoginPage loginPage;
public Login(){}
...
}
Steps:
#ContextConfiguration(classes = {AppConfig.class})
public class LoginSteps {
#Autowired
Login login;
public LoginSteps(){
}
#Given("^an? (admin|user) logs in$")
public void adminLogsIn(Login.User user) throws Exception {
World.currentScenario().write("Logging in as " + user + "\n");
login.as(user);
}
}
Error:
cucumber.runtime.CucumberException: Error creating bean with name 'LoginSteps': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: Login LoginSteps.login; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'login': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private LoginPage Login.loginPage; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'loginPage' defined in AppConfig: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [LoginPage]: Factory method 'loginPage' threw exception; nested exception is java.lang.NullPointerException
And now for the fun part...
BrowserFactory in World class is properly Autowired!!
World:
public class World {
#Autowired
private BrowserFactory browserFactory;
...
}
So I'll answer my own question:)
Issue was that I was calling BrowserFactory inside of Page constructor.
Looks like this bean was not yet created and was causing NPEs.
In order to fix that I:
Added #Lazy annotation to configuration (all elements that use this configuration, both defined in that class and those which will be found by Scan will be created as Lazy)
Moved call to Browser Factory to #PostConstruct method
Two more things to increase readability of Spring config:
Added #ComponentScan to configuration
Classes with no constructor parameters are annotated with #Component and #Scope("cucumber-glue") annotation so bean creation can be removed from AppConfig.class

Resources