# 7.9.4. 测试容器

[Testcontainers](https://www.testcontainers.org/)库提供了一种管理 Docker 容器内运行的服务的方法。它与 JUnit 集成，允许您编写一个测试类，该测试类可以在任何测试运行之前启动容器。Testcontainers 对于编写与真实后端服务（例如 MySQL、MongoDB、Cassandra 等）通信的集成测试特别有用。

测试容器可以在 Spring Boot 测试中使用，如下所示：

```
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
​
    @Container
    static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");
​
    @Test
    void myTest() {
        // ...
    }
​
}
```

这将在运行任何测试之前启动一个运行 Neo4j 的 docker 容器（如果 Docker 在本地运行）。在大多数情况下，您需要配置应用程序以连接到容器中运行的服务。

**服务连接**

服务连接是与任何远程服务的连接。Spring Boot 的自动配置可以使用服务连接的详细信息，并使用它们来建立与远程服务的连接。执行此操作时，连接详细信息优先于任何与连接相关的配置属性。

使用测试容器时，可以通过注释测试类中的容器字段来自动为容器中运行的服务创建连接详细信息。

```
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
​
    @Container
    @ServiceConnection
    static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");
​
    @Test
    void myTest() {
        // ...
    }
​
}
```

由于`@ServiceConnection`，上述配置允许应用程序中与 Neo4j 相关的 bean 与 Testcontainers 管理的 Docker 容器内运行的 Neo4j 进行通信。这是通过自动定义一个`Neo4jConnectionDetails`bean 来完成的，然后由 Neo4j 自动配置使用该 bean，覆盖任何与连接相关的配置属性。

> 您需要将该`spring-boot-testcontainers`模块添加为测试依赖项，以便将服务连接与测试容器一起使用。

服务连接注解由注册到`spring.factories` 的 `ContainerConnectionDetailsFactory` 类处理。 `ContainerConnectionDetailsFactory` 可以基于特定的 `Container` 子类或 `Docker` 映像名称创建 `ConnectionDetails`bean。

`spring-boot-testcontainers`jar中提供了以下服务连接工厂：

| 连接详情                             | 匹配于                                                                                                      |
| -------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `ActiveMQConnectionDetails`      | 名为“symptoma/activemq”的容器                                                                                 |
| `CassandraConnectionDetails`     | 容器类型`CassandraContainer`                                                                                 |
| `CouchbaseConnectionDetails`     | 容器类型`CouchbaseContainer`                                                                                 |
| `ElasticsearchConnectionDetails` | 容器类型`ElasticsearchContainer`                                                                             |
| `FlywayConnectionDetails`        | 容器类型`JdbcDatabaseContainer`                                                                              |
| `JdbcConnectionDetails`          | 容器类型`JdbcDatabaseContainer`                                                                              |
| `KafkaConnectionDetails`         | 类型`KafkaContainer`或`RedpandaContainer`的容器                                                                |
| `LiquibaseConnectionDetails`     | 容器类型`JdbcDatabaseContainer`                                                                              |
| `MongoConnectionDetails`         | 容器类型`MongoDBContainer`                                                                                   |
| `Neo4jConnectionDetails`         | 容器类型`Neo4jContainer`                                                                                     |
| `OtlpMetricsConnectionDetails`   | 名为“otel/opentelemetry-collector-contrib”的容器                                                              |
| `OtlpTracingConnectionDetails`   | 名为“otel/opentelemetry-collector-contrib”的容器                                                              |
| `PulsarConnectionDetails`        | 容器类型`PulsarContainer`                                                                                    |
| `R2dbcConnectionDetails`         | 类型为`MariaDBContainer`、`MSSQLServerContainer`、`MySQLContainer`、`OracleContainer`或`PostgreSQLContainer`的容器 |
| `RabbitConnectionDetails`        | 容器类型`RabbitMQContainer`                                                                                  |
| `RedisConnectionDetails`         | 名为“redis”的容器                                                                                             |
| `ZipkinConnectionDetails`        | 名为“openzipkin/zipkin”的容器                                                                                 |

> 默认情况下，将为给定的`Container`创建所有适用的连接详细信息 bean 。例如， `PostgreSQLContainer`将创建`JdbcConnectionDetails`和`R2dbcConnectionDetails`。
>
> 如果您只想创建适用类型的子集，则可以使用`type`的属性`@ServiceConnection`。

默认情况下`Container.getDockerImageName()`用于获取用于查找连接详细信息的名称。只要 Spring Boot 能够获取`Container` 的实例，这种情况就有效，就像使用上面示例中的`static`字段时的情况一样。

如果您使用`@Bean`方法，Spring Boot 将不会调用 bean 方法来获取 Docker 映像名称，因为这会导致急切的初始化问题。相反，bean 方法的返回类型用于找出应使用哪个连接详细信息。只要您使用类型化容器（例如`Neo4jContainer`或 `RabbitMQContainer`） ，此方法就有效。如果您正在使用（例如 Redis），`GenericContainer`将停止工作，如以下示例所示：

```
@TestConfiguration(proxyBeanMethods = false)
public class MyRedisConfiguration {
​
    @Bean
    @ServiceConnection(name = "redis")
    public GenericContainer<?> redisContainer() {
        return new GenericContainer<>("redis:7");
    }
​
}
```

Spring Boot 无法判断`GenericContainer`使用的是哪个容器映像，因此必须使用`@ServiceConnection`的`name` 属性来提供该提示。

您还可以使用`@ServiceConnection` 的`name`属性来覆盖将使用的连接详细信息，例如在使用自定义图像时。如果您使用 Docker 映像`registry.mycompany.com/mirror/myredis`，则需要`@ServiceConnection(name="redis")`确保`RedisConnectionDetails`已创建。

**动态属性**

服务连接的一个稍微详细但也更灵活的替代方案是`@DynamicPropertySource`。静态`@DynamicPropertySource`方法允许向 Spring 环境添加动态属性值。

```
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
​
    @Container
    static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");
​
    @Test
    void myTest() {
        // ...
    }
​
    @DynamicPropertySource
    static void neo4jProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.neo4j.uri", neo4j::getBoltUrl);
    }
​
}
```

上述配置允许应用程序中与 Neo4j 相关的 bean 与 Testcontainers 管理的 Docker 容器内运行的 Neo4j 进行通信。
