使用 Testcontainer 进行 Spring 集成测试 - 数据库在应用程序之后启动

Posted

技术标签:

【中文标题】使用 Testcontainer 进行 Spring 集成测试 - 数据库在应用程序之后启动【英文标题】:Spring integration Testing with Testcontainer - Database starting after Application 【发布时间】:2020-11-24 14:38:43 【问题描述】:

我用spring(core)测试的App想通过testcontainer连接数据库,但是此时testcontainer没有启动。如何实现测试容器准备就绪,然后启动整个上下文?

 import org.junit.jupiter.api.Order;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
 import org.testcontainers.containers.PostgreSQLContainer;
 import org.testcontainers.junit.jupiter.Container;
 import org.testcontainers.junit.jupiter.Testcontainers;

@Testcontainers
@SpringJUnitWebConfig(locations = "classpath:it-context.xml")
public class IntegrationTest 


    @Container
    private PostgreSQLContainer postgresqlContainer = new PostgreSQLContainer();

    @Autowired
    private StatusBean status;

    @Test
    public void appStartedSuccessful()
        // app & database successful started and connected
        System.out.println(postgresqlContainer.getJdbcUrl());
        System.out.println(postgresqlContainer.getTestQueryString());
        System.out.println(status.getStartupTimestamp());
    


【问题讨论】:

您是否可能不小心使用了 JUnit 4 中的@Test?如果没有,您可以添加测试和所有导入的完整堆栈跟踪吗?也许本指南也可能有所帮助:rieckpil.de/… 唯一的解决方案是将容器设置为“最终”和“静态”,以便在测试开始前创建一次数据库。 【参考方案1】:

您可能希望等待容器启动并准备好

查看documentation 尽管code 似乎表明容器应该正在等待一个神奇的日志条目,也许由于某种原因这对你不起作用。

如果你在你的测试方法中Thread.sleep,Pg容器最终是否如预期到达?容器日志中的任何内容都表明某种失败(不太可能,因为 static 技巧对您有用)。

我在JUnit5 integration docs 中没有看到任何表明使用@Container 时需要手动等待的任何内容。

【讨论】:

问题是 spring 上下文在测试容器之前启动 - 即使等待。通过注释魔术 spring 将启动上下文,然后缺少数据库。 啊,是的,我觉得这听起来很熟悉。有人可能会说,您的应用程序应该能够在短时间内处理丢失的数据库,也许要等到它准备好后再开始启动。但是,您的解决方案现在可以正常工作,这可能更重要。 TestContainers 记录了这种静态启动技术,尽管机制稍微复杂一些。 如果您使用我上面帖子中提到的“静态类初始化器”方式,测试将等到数据库准备好,不是吗?【参考方案2】:

您可以编写 Datasource Bean 类集成测试并从 postgresqlContainer 中的参数创建数据源

 @Configuration
    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    @Testcontainers    
    public class TestConfiguration

       PostgreSQLContainer<?> postgres = 
             new PostgreSQLContainer<>(POSTGRES_TEST_IMAGE)
             .withInitScript("somepath/init_postgresql.sql")


            @Primary     
            @Bean     
            public DataSource dataSource()         
            HikariConfig hikariConfig=new HikariConfig();  
            hikariConfig.setJdbcUrl(postgres.getUrl());    
            hikariConfig.setUsername(postgres.getUsername());  
            hikariConfig.setPassword(postgres.getPassword());   
            hikariConfig.setDriverClassName(postgres.getDriverClassName());         
            HikariDataSource hikariDataSource=new HikariDataSource(hikariConfig);         
            return hikariDataSource;     
            
      

并将上面的配置类导入到你的springboot测试中,它会自动用TestContainer的Postgres数据源覆盖数据源。

你的测试应该是这样的

@ExtendWith(SpringExtension.clas)
@SpringBootTest(classes = DataSourceConfiguration.class, TestConfiguration.class)
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Testcontainers
public class RepoTest 

    @Autowired
    TestRepository testRepository;

    @Test
    public void testRepo() 
        System.out.println("*****************" + testRepository.existsById(1L));
    


详情请参考此链接: https://medium.com/@arpitsharma14/testcontainer-springboot-tests-faa05b71a7dc

【讨论】:

【参考方案3】:

在 SpringBoot 中,您必须使用来自 Postgres-Testcontainer 的 ApplicationContextInitializer 覆盖 DB 参数。测试容器生命周期管理将处理其余部分。应该看起来像这样:

@Testcontainers
@SpringJUnitWebConfig(locations = "classpath:it-context.xml")
@ContextConfiguration(initializers = IntegrationTest.Initializer.class)
public class IntegrationTest 

    @Container
    private static PostgreSQLContainer postgresqlContainer = new PostgreSQLContainer();

    static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> 
      public void initialize(ConfigurableApplicationContext configurableApplicationContext) 
        TestPropertyValues.of(
          "spring.datasource.url=" + postgresqlContainer.getJdbcUrl(),
          "spring.datasource.username=" + postgresqlContainer.getUsername(),
          "spring.datasource.password=" + postgresqlContainer.getPassword()
        ).applyTo(configurableApplicationContext.getEnvironment());
      
    

    @Autowired
    private StatusBean status;

    @Test
    public void appStartedSuccessful()
        // app & database successful started and connected
        System.out.println(postgresqlContainer.getJdbcUrl());
        System.out.println(postgresqlContainer.getTestQueryString());
        System.out.println(status.getStartupTimestamp());
    


【讨论】:

以上是关于使用 Testcontainer 进行 Spring 集成测试 - 数据库在应用程序之后启动的主要内容,如果未能解决你的问题,请参考以下文章

如何运行自定义 docker 镜像 testContainer

在 Spring Boot 集成测试中使用 TestContainer 填充数据库

在 gitlab 管道中执行 testcontainer 集成测试

在 Micronaut 应用程序中使用 TestContainer 的测试环境的 ApplicationContext 不起作用

在 springboottest 中从默认 applicaton.yml 设置 testcontainer 属性的更好方法

使用 Micronaut 应用程序为 JUnit5 中的每个单元/集成测试运行一次 TestContainer