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.properties
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 正在运行所有测试:

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”(为所有测试类提升单个容器)的主要内容,如果未能解决你的问题,请参考以下文章

OUTLOOK 无法验证安全证书问题

使用 testcontainers 测试 kafka 和 spark

SpringBoot 集成测试 Sybase 和 Testcontainers

带有 Kotlin、TestContainers 和外部配置的 SpringBootTest

outlook2007,每次发邮件都提示“无法验证您连接到的服务器使用的安全证书。目标主要名称不正确”,求助?

如何将 Testcontainers 与 @DataJpaTest 结合使用以避免代码重复?