Spring Boot 3.1 中改进了对Testcontainer支持


Testcontainers 是一个开源框架,用于提供数据库、消息代理、Web 浏览器或任何可以在 Docker 容器中运行的东西的一次性轻量级实例。

Spring Boot 中对Testcontainers的支持已经有一段时间了,Spring Boot 3.1 进一步改进了它。

在 Spring Boot 3.1 中,添加了两个与Testcontainers相关的新功:这两个功能都是在抽象之上实现的ConnectionDetails

第一个功能使Testcontainers的集成测试变得更加容易。
新的@ServiceConnection注解可以用在你测试的容器实例字段上:

@SpringBootTest
@Testcontainers
class MyIntegrationTests {

    @Container
    @ServiceConnection
    static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");

    @Test
    void myTest() {
        // ...
    }

}

这取代了过去需要编码@DynamicPropertySource步骤。

在封面下,@ServiceConnection会发现被注释的容器类型,并为其创建一个ConnectionDetails Bean。

在我们的例子中,这个bean是Neo4jConnectionDetails。Spring Boot对Neo4j的自动配置会消耗这个Bean,并配置驱动程序以连接到Testcontainer中运行的Neo4j服务器。这适用于Testcontainers所支持的许多不同的容器类型。如果你使用的是GenericContainer,我们会看一下镜像的名字来推断容器的类型。如果你使用的是我们不认识的自定义图像,你可以使用@ServiceConnection注解的name属性来为我们指出正确的方向。

用@ServiceConnection注释容器字段有几个好处:

  • 首先,你需要输入更少的代码。
  • 第二,你的集成测试和Spring Boot的自动配置之间不再有通过属性的 "字符串 "类型的耦合。
  • 第三,你不需要查找(或记住)属性名称。

我们认为这是一个非常好的功能,足以成为升级到Spring Boot 3.1的理由。如果你还不相信,让我们向你展示另一个伟大的功能:在开发阶段Testcontainers。

开发阶段Testcontainers
大多数应用程序都需要某种外部服务,例如,PostgreSQL数据库、Redis服务器或Zipkin后端。通常,这些服务是通过在接触代码之前运行readme中的一些docker运行命令来提供的,或者你使用类似Docker Compose的东西(Spring Boot 3.1也为此增加了一些很酷的新功能)。

在开发时使用Testcontainers,你现在在你的工具箱中又多了一个工具。为什么要将Testcontainers仅用于集成测试?
从技术上讲,没有什么可以阻止你在生产代码中启动Testcontainers,然后把属性放在适当的位置来连接到这些容器。这在现在是可行的,即使是3.1之前的Spring Boot。

这种方法的缺点是,你现在需要把Testcontainers的依赖关系放在你的编译classpath上,而且很有可能这个依赖关系也包含在你的fat JAR中。

在Spring Boot 3.1中,有一个更好的方法:将Testcontainers依赖性留在测试范围内。你所需要做的就是在你的测试代码中创建一个新的主方法:

public class TestMyApplication {

    public static void main(String[] args) {
        SpringApplication.from(MyApplication::main).run(args);
    }

}

这个测试主方法使用新的SpringApplication.from方法来委托给生产代码中的 "真正 "主方法。

现在你可以创建一个@TestConfiguration,它为你在开发应用程序时需要的Testcontainers定义了bean:

@TestConfiguration(proxyBeanMethods = false)
class MyContainersConfiguration {

    @Bean
    @ServiceConnection
    Neo4jContainer<?> neo4jContainer() {
        return new Neo4jContainer<>("neo4j:5");
    }

}

请注意,这个Bean方法被注解为@ServiceConnection,这样Spring Boot就会自动建立与容器中运行的服务的连接。这个容器的生命周期是由Spring Boot管理的。我们在应用程序启动时启动该容器,并在应用程序停止时关闭它。

有了这些,回到你的test-main方法,把它指向新创建的@TestConfiguration:

public class TestMyApplication {

    public static void main(String[] args) {
        SpringApplication.from(MyApplication::main)
            .with(MyContainersConfiguration.class)
            .run(args);
    }

}

现在你可以从你的IDE中启动这个test-main方法,容器会自动启动,Spring Boot会与它们建立连接。

你不需要设置任何配置属性,而且Spring Boot确保在你的应用程序停止时关闭容器。

如果你喜欢从终端运行你的应用程序,我们也会帮你解决这个问题。
用于Gradle和Maven的Spring Boot插件学会了运行这个test-main方法。

  • 在Gradle中,它是./gradlew bootTestRun,
  • 在Maven中是./mvnw spring-boot:test-run。

有一点需要注意的是,每次重启应用程序时,你的容器都会关闭,随之而来的是它们的数据丢失。

这可以通过两种方式解决:
第一种是使用Spring Boot开发工具,然后用@RestartScope来注释你的容器的bean方法。当devtools重新启动你的应用程序时,这样的容器不会被重新启动。这意味着你不必在每次改变应用程序中的内容时等待容器的启动,而且容器会保留它们的数据。

第二种方式是Testcontainers中的一个名为reusable containers的功能:

@TestConfiguration(proxyBeanMethods = false)
public class MyContainersConfiguration {

    @Bean
    @ServiceConnection
    public Neo4jContainer<?> neo4jContainer() {
        return new Neo4jContainer<>("neo4j:5").withReuse(true);
    }

}

当应用程序关闭时,这样的容器不会被停止。这是一个试验性的Testcontainers功能,所以使用它的风险由你自己承担。

为了完整起见,这里是我们目前支持的容器列表:

  • CassandraContainer
  • CouchbaseContainer
  • ElasticsearchContainer
  • GenericContainer using redis or openzipkin/zipkin
  • JdbcDatabaseContainer
  • KafkaContainer
  • MongoDBContainer
  • MariaDBContainer
  • MSSQLServerContainer
  • MySQLContainer
  • Neo4jContainer
  • OracleContainer
  • PostgreSQLContainer
  • RabbitMQContainer
  • RedpandaContainer