TestContainers and Error : "Failed to validate connection org.postgresql.jdbc.PgConnection" (raising a single container for all test classes)

TestContainers and Error : "Failed to validate connection org.postgresql.jdbc.PgConnection" (raising a single container for all test classes)

我尝试 运行 一项一项地进行测试时遇到问题。 数据库连接已关闭。

根据文档 (Containers declared as static fields ...),我试图确保我的容器在所有测试中都被提升一次。

我专门用它来为 Spring 测试容器提供 应用程序上下文 引发一次并用于所有测试。

确实如此,因为我在每次测试中都会检查:

      boolean running = getPostgreSQLContainer().isRunning();

        System.out.println(running);

也就是说,测试 运行 会自动一个接一个地进行。

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


@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestcontainersSpringBootClassruleApplicationTests extends TestPostgresContainer {

    @Autowired
    protected TestRestTemplate testRestTemplate;


    @Test
    @DisplayName("Should start the container")
    public void test() {

        boolean running = getPostgreSQLContainer().isRunning();

        System.out.println(running);
    }
}
class EmployeeRestControllerTest extends TestcontainersSpringBootClassruleApplicationTests {

    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() {
    }
}

测试classes位于不同目录

spring.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 已经过 运行ning 所有测试:

com.zaxxer.hikari.pool.PoolBase : HikariPool-1 - Failed to validate connection org.postgresql.jdbc.PgConnection@698af960 (Соединение уже было закрыто). Possibly consider using a shorter maxLifetime value.

我设置了一个值

spring.datasource.hikari.max-life = 600000

没用。

但是当我运行一次测试class时,就没有错误了。

我找到了这个:

Running container in daemon mode By default database container is being stopped as soon as last connection is closed. There are cases when you might need to start container and keep it running till you stop it explicitly or JVM is shutdown. To do this, add TC_DAEMON parameter to the URL as follows:

jdbc:tc:mysql:5.7.22:///databasename?TC_DAEMON=true

但是,我在哪里可以添加 TC_DAEMON=true 到 ?

我没有直接指定 url 本身。它已完成 TestContainers。

有了这个参数,即使没有打开的连接,数据库容器也会保持 运行ning。

更新

我编辑了这个:

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

这也没有帮助。

我 运行 没主意了。

有人知道如何解决这个问题吗?

您正在使用控制容器生命周期的 JUnit Jupiter 扩展 (@Testcontainers)。此扩展支持 two modes:

  1. 为每个测试方法重新启动的容器
  2. 在测试的所有方法之间共享的容器class

因此,对于您的设置,Testcontaienrs 为每个 test class 启动一个数据库,该数据库 not 在多个测试 classes 但同一测试中的测试方法 class.

考虑到您问题中的所有代码层次结构和代码更新,很难说问题出在哪里。

如果您想重复使用数据库容器并且只启动一次,请查看 Singelton containers and the reusability feature

PS:您不需要 @RunWith(SpringRunner.class) 因为您是 运行 您使用 JUnit 5 和 JUnit Jupiter 扩展 (SpringExtension) 的测试已经注册为@SpringBootTest

的一部分

解决方案

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 TestcontainersSpringBootClassruleApplicationTests 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 TestcontainersSpringBootClassruleApplicationTests {

    private static EmployeeDto employeeDto;

   @BeforeAll
   static void createUser(){
...
public class EmployeeReadRestControllerTest extends TestcontainersSpringBootClassruleApplicationTests {

    @Test
    void getAllEmployees() {
  1. 我没有使用注解@Testcontainers

  2. 我没有使用resources/testcontainers.properties(不太明白为什么需要这个文件,毕竟一切正常原样。)

  3. 很重要:

## 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);
    }
  1. 这个没必要(至少没必要运行 Testcontainers)。不知道为什么。
@TestPropertySource("classpath:application.properties")

相反 @DynamicPropertySource 有效。

我想补充几条评论:

  • 1 如果您使用 .withReuse(true) 方法,在您完成 运行 测试后。您必须从容器中清理 docker space。

在这种情况下,Ryuk不会为你做。

 postgreSQLContainer = (PostgreSQLContainer) new PostgreSQLContainer(postgres)
      .withDatabaseName("test")
      .withUsername("name")
      .withPassword("password")
      .withReuse(true);
    1. 如果你只想增加测试时间运行,那么我建议使用Singleton containers。 那样的话,Ryuk会为你做的。

谢谢。