此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 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
or RedpandaContainer
Containers of type JdbcDatabaseContainer
Containers of type MongoDBContainer
Containers of type Neo4jContainer
Containers named "otel/opentelemetry-collector-contrib"
Containers named "otel/opentelemetry-collector-contrib"
Containers of type PulsarContainer
Containers of type MariaDBContainer
, MSSQLServerContainer
, MySQLContainer
, OracleContainer (free), OracleContainer (XE) or PostgreSQLContainer
Containers of type RabbitMQContainer
Containers named "redis"
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, e.g. Neo4jContainer
or RabbitMQContainer
.
This stops working if you’re using GenericContainer
, e.g. 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.
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.