TestContainers 和错误:“无法验证连接 org.postgresql.jdbc.PgConnection”(为所有测试类提升单个容器)
Posted
技术标签:
【中文标题】TestContainers 和错误:“无法验证连接 org.postgresql.jdbc.PgConnection”(为所有测试类提升单个容器)【英文标题】:TestContainers and Error : "Failed to validate connection org.postgresql.jdbc.PgConnection" (raising a single container for all test classes) 【发布时间】:2021-04-04 01:43:15 【问题描述】:当我尝试逐个运行测试时遇到问题。 数据库连接已关闭。
根据文档 (Containers declared as static fields ...),我试图确保我的容器在所有测试中都被提升一次。
我专门使用它来为 Spring 和一个 test-container 设置一次 application context 并用于所有测试。 p>
确实如此,因为我在每个测试中都会进行检查:
boolean running = getPostgreSQLContainer().isRunning();
System.out.println(running);
也就是说,测试会一个一个地自动运行。
pom.xml <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
....
<properties>
<java.version>11</java.version>
<testcontainers.version>1.15.1</testcontainers.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<version.mapstruct>1.4.1.Final</version.mapstruct>
<version.maven.compiler.plugin>3.8.1</version.maven.compiler.plugin>
<version.embedded.postgresql.testcontainers>1.86</version.embedded.postgresql.testcontainers>
<version.spring.cloud.starter>2.2.6.RELEASE</version.spring.cloud.starter>
</properties>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>$version.mapstruct</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>$version.mapstruct</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>$testcontainers.version</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
TestPostgresContainer
@Testcontainers
@TestPropertySource("classpath:application.properties")
public class TestPostgresContainer
private static String dataBaseName;
private static String userNameBase;
private static String passwordBase;
public TestPostgresContainer()
private static DockerImageName postgres;
static
postgres = DockerImageName.parse("postgres:13.1");
dataBaseName = PropertiesExtractor.getProperty("database.name.test.container");
userNameBase = PropertiesExtractor.getProperty("username.testcontainer");
passwordBase = PropertiesExtractor.getProperty("password.testcontainer");
@SuppressWarnings("rawtypes")
@Container
private static PostgreSQLContainer postgreSQLContainer = (PostgreSQLContainer) new PostgreSQLContainer(postgres)
.withDatabaseName(dataBaseName)
.withUsername(userNameBase)
.withPassword(passwordBase)
.withStartupTimeout(Duration.ofSeconds(600));
@SuppressWarnings("rawtypes")
public static PostgreSQLContainer getPostgreSQLContainer()
return postgreSQLContainer;
/**
* It need for Spring boot 2.2.6 and higher.
*/
@DynamicPropertySource
static void properties(DynamicPropertyRegistry propertyRegistry)
propertyRegistry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
propertyRegistry.add("spring.datasource.username", postgreSQLContainer::getUsername);
propertyRegistry.add("spring.datasource.password", postgreSQLContainer::getPassword);
TestcontainersSpringBootClas-s-ruleApplicationTests
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestcontainersSpringBootClas-s-ruleApplicationTests extends TestPostgresContainer
@Autowired
protected TestRestTemplate testRestTemplate;
@Test
@DisplayName("Should start the container")
public void test()
boolean running = getPostgreSQLContainer().isRunning();
System.out.println(running);
EmployeeRestControllerTest
class EmployeeRestControllerTest extends TestcontainersSpringBootClas-s-ruleApplicationTests
private static EmployeeDto employeeDto;
@BeforeAll
static void createUser()
PostgreSQLContainer postgreSQLContainer = getPostgreSQLContainer();
employeeDto = EmployeeDto
.newBuilder()
.firstName("Joanna")
.lastName("Soyer")
.country("germany")
.build();
@Transactional
@Test
void addEmployee()
boolean running = getPostgreSQLContainer().isRunning();
System.out.println(running);
String url = "/employees/addEmployee";
HttpEntity<EmployeeDto> entity = new HttpEntity<>(employeeDto);
ResponseEntity<EmployeeDto> employeeDtoResponseEntity =
testRestTemplate.exchange(url, HttpMethod.POST, entity, EmployeeDto.class);
HttpStatus statusCode = employeeDtoResponseEntity.getStatusCode();
assertThat(statusCode, is(HttpStatus.OK));
@Test
void getAllEmployees()
测试类位于不同的目录
application.propertiesspring.main.banner-mode=off
spring.datasource.initialization-mode=always
## PostgreSQL for TestContainers
database.name.test.container=integration-tests-db
username.testcontainer=root
password.testcontainer=root
spring.datasource.hikari.max-life = 600000
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.liquibase.change-log=classpath:/db/changelog/db.changelog-master-test.xml
我使用 liquibase。
Wnen 正在运行所有测试:
com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - 无法验证连接 org.postgresql.jdbc.PgConnection@698af960 (Соединение уже было закрыто)。可能考虑使用较短的 maxLifetime 值。
我设置了一个值
spring.datasource.hikari.max-life = 600000
没用。
但是当我一次运行一个测试类时,就没有错误了。
我发现了这个:
在守护进程模式下运行容器 默认情况下,一旦关闭最后一个连接,数据库容器就会停止。在某些情况下,您可能需要启动容器并使其保持运行,直到您明确停止它或 JVM 关闭。为此,请将 TC_DAEMON 参数添加到 URL,如下所示:
jdbc:tc:mysql:5.7.22:///databasename?TC_DAEMON=true
但是,我在哪里可以将 TC_DAEMON=true 添加到?
我不直接指定url本身。。是TestContainers做的。
使用此参数,即使没有打开的连接,数据库容器也会继续运行。
更新
我编辑了这个:
@Container
private static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer(postgres)
.withDatabaseName(dataBaseName)
.withUsername(userNameBase)
.withPassword(passwordBase);
和
@DynamicPropertySource
static void registerPgProperties(DynamicPropertyRegistry propertyRegistry)
String jdbcUrlPart = getPostgreSQLContainer().getJdbcUrl();
String jdbcUrlFull = jdbcUrlPart + "&TC_DAEMON=true";
propertyRegistry.add("integration-tests-db", getPostgreSQLContainer()::getDatabaseName);
propertyRegistry.add("spring.datasource.username", getPostgreSQLContainer()::getUsername);
propertyRegistry.add("spring.datasource.password", getPostgreSQLContainer()::getPassword);
propertyRegistry.add("spring.datasource.url", () -> jdbcUrlFull);
这也没有帮助。
我的想法已经用完了。
有人知道如何解决这个问题吗?
【问题讨论】:
【参考方案1】:您正在使用控制容器生命周期的 JUnit Jupiter 扩展 (@Testcontainers
)。此扩展支持two modes:
-
为每个测试方法重新启动的容器
在测试类的所有方法之间共享的容器
因此,对于您的设置,Testcontaiens 为每个测试类启动一个数据库,该数据库不在多个测试类之间共享,而是在同一个测试类中的测试方法。
由于您问题中的所有代码层次结构和代码更新,很难说问题出在哪里。
如果您想重复使用数据库容器并且只启动一次,请查看Singelton containers 和reusability feature。
PS:您不需要 @RunWith(SpringRunner.class)
,因为您正在使用 JUnit 5 运行测试,并且 JUnit Jupiter 扩展 (SpringExtension
) 已注册为 @SpringBootTest
的一部分
【讨论】:
@RunWith(SpringRunner.class) - 我删除了那个。我同意这一点。【参考方案2】:解决方案
public class TestPostgresContainer
@SuppressWarnings("rawtypes")
private static PostgreSQLContainer postgreSQLContainer;
public TestPostgresContainer()
static
DockerImageName postgres = DockerImageName.parse("postgres:13.1");
postgreSQLContainer = (PostgreSQLContainer) new PostgreSQLContainer(postgres)
.withReuse(true);
postgreSQLContainer.start();
....
@SuppressWarnings("unused")
@DynamicPropertySource
static void registerPgProperties(DynamicPropertyRegistry propertyRegistry)
String jdbcUrl = getPostgreSQLContainer().getJdbcUrl();
propertyRegistry.add("integration-tests-db", getPostgreSQLContainer()::getDatabaseName);
propertyRegistry.add("spring.datasource.username", getPostgreSQLContainer()::getUsername);
propertyRegistry.add("spring.datasource.password", getPostgreSQLContainer()::getPassword);
propertyRegistry.add("spring.datasource.url", () -> jdbcUrl);
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestcontainersSpringBootClas-s-ruleApplicationTests extends TestPostgresContainer
@Autowired
protected TestRestTemplate testRestTemplate;
@Test
@DisplayName("Should start the container")
public void test()
boolean running = getPostgreSQLContainer().isRunning();
System.out.println("Is The container run : " + running);
class EmployeeRestControllerTest extends TestcontainersSpringBootClas-s-ruleApplicationTests
private static EmployeeDto employeeDto;
@BeforeAll
static void createUser()
...
public class EmployeeReadRestControllerTest extends TestcontainersSpringBootClas-s-ruleApplicationTests
@Test
void getAllEmployees()
我没有使用注释@Testcontainers
我没有使用 resources/testcontainers.properties(我不太明白为什么需要这个文件。毕竟,一切都按原样运行。)
这很重要:
## PostgreSQL for TestContainers
database.name.test.container=integration-tests-db
username.testcontainer=root
password.testcontainer=root
和
@DynamicPropertySource
static void registerPgProperties(DynamicPropertyRegistry propertyRegistry)
String jdbcUrl = getPostgreSQLContainer().getJdbcUrl();
propertyRegistry.add("integration-tests-db", getPostgreSQLContainer()::getDatabaseName);
propertyRegistry.add("spring.datasource.username", getPostgreSQLContainer()::getUsername);
propertyRegistry.add("spring.datasource.password", getPostgreSQLContainer()::getPassword);
propertyRegistry.add("spring.datasource.url", () -> jdbcUrl);
-
这不是必需的(至少不需要运行 Testcontainers)。我不知道为什么。
@TestPropertySource("classpath:application.properties")
改为 @DynamicPropertySource 有效。
我想添加几个cmets:
1 如果你使用 .withReuse(true) 方法,在你完成后运行测试。您必须从容器中清理 docker 空间。在这种情况下,Ryuk 不会为你做这件事。
postgreSQLContainer = (PostgreSQLContainer) new PostgreSQLContainer(postgres)
.withDatabaseName("test")
.withUsername("name")
.withPassword("password")
.withReuse(true);
-
如果你只想增加运行测试的时间,那么我推荐使用Singleton containers。
在这种情况下,Ryuk 会为您完成。
谢谢。
【讨论】:
以上是关于TestContainers 和错误:“无法验证连接 org.postgresql.jdbc.PgConnection”(为所有测试类提升单个容器)的主要内容,如果未能解决你的问题,请参考以下文章
使用 testcontainers 测试 kafka 和 spark
SpringBoot 集成测试 Sybase 和 Testcontainers
带有 Kotlin、TestContainers 和外部配置的 SpringBootTest