SpringBootTest、Testcontainers、容器启动-容器启动后才能获取映射端口
SpringBootTest, Testcontainers, container start up - Mapped port can only be obtained after the container is started
我正在使用 docker/testcontainers 到 运行 postgresql 数据库进行测试。对于仅测试数据库访问的单元测试,我已经有效地完成了此操作。但是,我现在已经将 spring 引导测试纳入其中,以便我可以使用嵌入式 Web 服务进行测试,但我遇到了问题。
问题似乎是在容器启动之前请求了 dataSource bean。
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
这是我的 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");
}
}
这是我从测试中引用的 spring 启动应用程序:
@SpringBootApplication
public class TestApplication
{
public static void main(String[] args)
{
SpringApplication.run(TestApplication.class, args);
}
}
这是 IntegrationDataService class,它旨在启动容器并为其他所有内容提供 sessionfactory/datasource
@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;
}
}
在容器启动之前,从 Dao classes 之一的 sessionFactory 请求数据源 bean 时发生错误。
我到底做错了什么?
谢谢!!!
您的 java.lang.IllegalStateException: Mapped port can only be obtained after the container is started
异常的原因是当 Spring 上下文现在在您使用 @SpringBootTest
的测试期间创建时,它会尝试在应用程序启动时连接到数据库。
由于您只在 IntegrationDataService
class 中启动 PostgreSQL,因此存在时间问题,因为您无法获取 JDBC URL 或创建连接在应用程序启动时,因为尚未正确创建此 bean。
一般来说,您应该不在您的IntegrationDataService
class中使用任何test-related代码。 Starting/stopping 数据库应该在您的测试设置中完成。
这确保首先启动数据库容器,等到它启动并 运行,然后才启动实际测试并创建 Spring 上下文。
我总结了 JUnit 4/5 与 Testcontainers 和 Spring Boot 所需的设置机制,可以帮助您 get the setup right。
最后,这看起来像下面这样
// 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() {
}
}
我正在使用 docker/testcontainers 到 运行 postgresql 数据库进行测试。对于仅测试数据库访问的单元测试,我已经有效地完成了此操作。但是,我现在已经将 spring 引导测试纳入其中,以便我可以使用嵌入式 Web 服务进行测试,但我遇到了问题。
问题似乎是在容器启动之前请求了 dataSource bean。
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
这是我的 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");
}
}
这是我从测试中引用的 spring 启动应用程序:
@SpringBootApplication
public class TestApplication
{
public static void main(String[] args)
{
SpringApplication.run(TestApplication.class, args);
}
}
这是 IntegrationDataService class,它旨在启动容器并为其他所有内容提供 sessionfactory/datasource
@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;
}
}
在容器启动之前,从 Dao classes 之一的 sessionFactory 请求数据源 bean 时发生错误。
我到底做错了什么?
谢谢!!!
您的 java.lang.IllegalStateException: Mapped port can only be obtained after the container is started
异常的原因是当 Spring 上下文现在在您使用 @SpringBootTest
的测试期间创建时,它会尝试在应用程序启动时连接到数据库。
由于您只在 IntegrationDataService
class 中启动 PostgreSQL,因此存在时间问题,因为您无法获取 JDBC URL 或创建连接在应用程序启动时,因为尚未正确创建此 bean。
一般来说,您应该不在您的IntegrationDataService
class中使用任何test-related代码。 Starting/stopping 数据库应该在您的测试设置中完成。
这确保首先启动数据库容器,等到它启动并 运行,然后才启动实际测试并创建 Spring 上下文。
我总结了 JUnit 4/5 与 Testcontainers 和 Spring Boot 所需的设置机制,可以帮助您 get the setup right。
最后,这看起来像下面这样
// 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() {
}
}