此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Boot 3.4.3! |
测试容器
Testcontainers 库提供了一种管理在 Docker 容器中运行的服务的方法。 它与 JUnit 集成,允许您编写一个测试类,该类可以在任何测试运行之前启动容器。 Testcontainers 对于编写与实际后端服务(如 MySQL、MongoDB、Cassandra 等)通信的集成测试特别有用。
Testcontainers 可以在 Spring Boot 测试中使用,如下所示:
-
Java
-
Kotlin
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.test.context.SpringBootTest;
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
@Container
static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");
@Test
void myTest() {
// ...
}
}
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
@Test
fun myTest() {
// ...
}
companion object {
@Container
@JvmStatic
val neo4j = Neo4jContainer("neo4j:5");
}
}
This will start up a docker container running Neo4j (if Docker is running locally) before any of the tests are run.
In most cases, you will need to configure the application to connect to the service running in the container.
Service Connections
A service connection is a connection to any remote service.
Spring Boot’s auto-configuration can consume the details of a service connection and use them to establish a connection to a remote service.
When doing so, the connection details take precedence over any connection-related configuration properties.
When using Testcontainers, connection details can be automatically created for a service running in a container by annotating the container field in the test class.
-
Java
-
Kotlin
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
@Container
@ServiceConnection
static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");
@Test
void myTest() {
// ...
}
}
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
@Test
fun myTest() {
// ...
}
companion object {
@Container
@ServiceConnection
@JvmStatic
val neo4j = Neo4jContainer("neo4j:5");
}
}
Thanks to @ServiceConnection
, the above configuration allows Neo4j-related beans in the application to communicate with Neo4j running inside the Testcontainers-managed Docker container.
This is done by automatically defining a Neo4jConnectionDetails
bean which is then used by the Neo4j auto-configuration, overriding any connection-related configuration properties.
You’ll need to add the spring-boot-testcontainers
module as a test dependency in order to use service connections with Testcontainers.
Service connection annotations are processed by ContainerConnectionDetailsFactory
classes registered with spring.factories
.
A ContainerConnectionDetailsFactory
can create a ConnectionDetails
bean based on a specific Container
subclass, or the Docker image name.
The following service connection factories are provided in the spring-boot-testcontainers
jar:
Connection Details
Matched on
Containers named "symptoma/activemq" or ActiveMQContainer
Containers of type ArtemisContainer
Containers of type CassandraContainer
Containers of type CouchbaseContainer
Containers of type ElasticsearchContainer
Containers of type JdbcDatabaseContainer
Containers of type JdbcDatabaseContainer
Containers of type KafkaContainer
, ConfluentKafkaContainer
or RedpandaContainer
Containers named "osixia/openldap" or of type LLdapContainer
Containers of type JdbcDatabaseContainer
Containers of type MongoDBContainer
Containers of type Neo4jContainer
Containers named "otel/opentelemetry-collector-contrib" or of type LgtmStackContainer
Containers named "otel/opentelemetry-collector-contrib" or of type LgtmStackContainer
Containers named "otel/opentelemetry-collector-contrib" or of type LgtmStackContainer
Containers of type PulsarContainer
Containers of type ClickHouseContainer
, MariaDBContainer
, MSSQLServerContainer
, MySQLContainer
, OracleContainer (free), OracleContainer (XE) or PostgreSQLContainer
Containers of type RabbitMQContainer
Containers of type RedisContainer
or RedisStackContainer
, or containers named "redis", "redis/redis-stack" or "redis/redis-stack-server"
Containers named "openzipkin/zipkin"
By default all applicable connection details beans will be created for a given Container
.
For example, a PostgreSQLContainer
will create both JdbcConnectionDetails
and R2dbcConnectionDetails
.
If you want to create only a subset of the applicable types, you can use the type
attribute of @ServiceConnection
.
By default Container.getDockerImageName().getRepository()
is used to obtain the name used to find connection details.
The repository portion of the Docker image name ignores any registry and the version.
This works as long as Spring Boot is able to get the instance of the Container
, which is the case when using a static
field like in the example above.
If you’re using a @Bean
method, Spring Boot won’t call the bean method to get the Docker image name, because this would cause eager initialization issues.
Instead, the return type of the bean method is used to find out which connection detail should be used.
This works as long as you’re using typed containers such as Neo4jContainer
or RabbitMQContainer
.
This stops working if you’re using GenericContainer
, for example with Redis as shown in the following example:
-
Java
-
Kotlin
import org.testcontainers.containers.GenericContainer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
@TestConfiguration(proxyBeanMethods = false)
public class MyRedisConfiguration {
@Bean
@ServiceConnection(name = "redis")
public GenericContainer<?> redisContainer() {
return new GenericContainer<>("redis:7");
}
}
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.testcontainers.service.connection.ServiceConnection
import org.springframework.context.annotation.Bean
import org.testcontainers.containers.GenericContainer
@TestConfiguration(proxyBeanMethods = false)
class MyRedisConfiguration {
@Bean
@ServiceConnection(name = "redis")
fun redisContainer(): GenericContainer<*> {
return GenericContainer("redis:7")
}
}
Spring Boot can’t tell from GenericContainer
which container image is used, so the name
attribute from @ServiceConnection
must be used to provide that hint.
You can also use the name
attribute of @ServiceConnection
to override which connection detail will be used, for example when using custom images.
If you are using the Docker image registry.mycompany.com/mirror/myredis
, you’d use @ServiceConnection(name="redis")
to ensure RedisConnectionDetails
are created.
SSL with Service Connections
You can use the @Ssl
, @JksKeyStore
, @JksTrustStore
, @PemKeyStore
and @PemTrustStore
annotations on a supported container to enable SSL support for that service connection.
Please note that you still have to enable SSL on the service which is running inside the Testcontainer yourself, the annotations only configure SSL on the client side in your application.
import com.redis.testcontainers.RedisContainer;
import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.PemKeyStore;
import org.springframework.boot.testcontainers.service.connection.PemTrustStore;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.data.redis.core.RedisOperations;
@Testcontainers
@SpringBootTest
class MyRedisWithSslIntegrationTests {
@Container
@ServiceConnection
@PemKeyStore(certificate = "classpath:client.crt", privateKey = "classpath:client.key")
@PemTrustStore("classpath:ca.crt")
static RedisContainer redis = new SecureRedisContainer("redis:latest");
@Autowired
private RedisOperations<Object, Object> operations;
@Test
void testRedis() {
// ...
}
}
The above code uses the @PemKeyStore
annotation to load the client certificate and key into the keystore and the and @PemTrustStore
annotation to load the CA certificate into the truststore.
This will authenticate the client against the server, and the CA certificate in the truststore makes sure that the server certificate is valid and trusted.
The SecureRedisContainer
in this example is a custom subclass of RedisContainer
which copies certificates to the correct places and invokes redis-server
with commandline parameters enabling SSL.
The SSL annotations are supported for the following service connections:
-
Cassandra
-
Couchbase
-
Elasticsearch
-
Kafka
-
MongoDB
-
RabbitMQ
-
Redis
The ElasticsearchContainer
additionally supports automatic detection of server side SSL.
To use this feature, annotate the container with @Ssl
, as seen in the following example, and Spring Boot takes care of the client side SSL configuration for you:
import org.junit.jupiter.api.Test;
import org.testcontainers.elasticsearch.ElasticsearchContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.elasticsearch.DataElasticsearchTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.boot.testcontainers.service.connection.Ssl;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
@Testcontainers
@DataElasticsearchTest
class MyElasticsearchWithSslIntegrationTests {
@Ssl
@Container
@ServiceConnection
static ElasticsearchContainer elasticsearch = new ElasticsearchContainer(
"docker.elastic.co/elasticsearch/elasticsearch:8.17.2");
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Test
void testElasticsearch() {
// ...
}
}
Dynamic Properties
A slightly more verbose but also more flexible alternative to service connections is @DynamicPropertySource
.
A static @DynamicPropertySource
method allows adding dynamic property values to the Spring Environment.
-
Java
-
Kotlin
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
@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);
}
}
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.testcontainers.containers.Neo4jContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
@Testcontainers
@SpringBootTest
class MyIntegrationTests {
@Test
fun myTest() {
// ...
}
companion object {
@Container
@JvmStatic
val neo4j = Neo4jContainer("neo4j:5");
@DynamicPropertySource
@JvmStatic
fun neo4jProperties(registry: DynamicPropertyRegistry) {
registry.add("spring.neo4j.uri") { neo4j.boltUrl }
}
}
}
The above configuration allows Neo4j-related beans in the application to communicate with Neo4j running inside the Testcontainers-managed Docker container.