Skip to content

Commit 95f45ea

Browse files
wilkinsonaphilwebbmhalbritter
committed
Create service connections from Testcontainers-managed containers
Building upon the auto-configuration support for service connections, this commit adds support for deriving connection details from a Testcontainers-managed container. Several service-specific annotations have been introduced. These annotations can be used on a container field to indicate that it is a source of the details for a service connection. See gh-34658 Co-Authored-By: Phillip Webb <[email protected]> Co-Authored-By: Mortitz Halbritter <[email protected]>
1 parent 8ec266b commit 95f45ea

File tree

80 files changed

+2748
-264
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+2748
-264
lines changed

spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/testing.adoc

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,46 @@ Testcontainers can be used in a Spring Boot test as follows:
2929
include::code:vanilla/MyIntegrationTests[]
3030

3131
This will start up a docker container running Neo4j (if Docker is running locally) before any of the tests are run.
32-
In most cases, you will need to configure the application using details from the running container, such as container IP or port.
32+
In most cases, you will need to configure the application to connect to the service running in the container.
3333

34-
This can be done with a static `@DynamicPropertySource` method that allows adding dynamic property values to the Spring Environment.
3534

36-
include::code:dynamicproperties/MyIntegrationTests[]
35+
[[howto.testing.testcontainers.service-connections]]
36+
==== Service Connections
37+
A service connection is a connection to any remote service.
38+
Spring Boot's auto-configuration can consume the details of a service connection and use them to establish a connection to a remote service.
39+
When doing so, the connection details take precedence over any connection-related configuration properties.
40+
41+
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.
42+
43+
include::code:MyIntegrationTests[]
44+
45+
Thanks to `@Neo4jServiceConnection`, the above configuration allows Neo4j-related beans in the application to communicate with Neo4j running inside the Testcontainers-managed Docker container.
46+
This is done by automatically defining a `Neo4jConnectionDetails` bean which is then used by the Neo4j auto-configuration, overriding any connection-related configuration properties.
47+
48+
The following service connection annotations are provided by `spring-boot-test-autoconfigure`:
49+
50+
- `@CassandraServiceConnection`
51+
- `@CouchbaseServiceConnection`
52+
- `@ElasticsearchServiceConnection`
53+
- `@InfluxDbServiceConnection`
54+
- `@JdbcServiceConnection`
55+
- `@KafkaServiceConnection`
56+
- `@MongoServiceConnection`
57+
- `@Neo4jServiceConnection`
58+
- `@R2dbcServiceConnection`
59+
- `@RabbitServiceConnection`
60+
- `@RedisServiceConnection`
61+
62+
As with the earlier `@Neo4jConnectionDetails` example, each can be used on a container field. Doing so will automatically configure the application to connect to the service running in the container.
63+
64+
65+
66+
[[howto.testing.testcontainers.dynamic-properties]]
67+
==== Dynamic Properties
68+
A slightly more verbose but also more flexible alternative to service connections is `@DynamicPropertySource`.
69+
A static `@DynamicPropertySource` method allows adding dynamic property values to the Spring Environment.
70+
71+
include::code:/MyIntegrationTests[]
3772

3873
The above configuration allows Neo4j-related beans in the application to communicate with Neo4j running inside the Testcontainers-managed Docker container.
3974

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docs.howto.testing.testcontainers.serviceconnections;
18+
19+
import org.junit.jupiter.api.Test;
20+
import org.testcontainers.containers.Neo4jContainer;
21+
import org.testcontainers.junit.jupiter.Container;
22+
import org.testcontainers.junit.jupiter.Testcontainers;
23+
24+
import org.springframework.boot.test.autoconfigure.neo4j.Neo4jServiceConnection;
25+
import org.springframework.boot.test.context.SpringBootTest;
26+
27+
@SpringBootTest
28+
@Testcontainers
29+
class MyIntegrationTests {
30+
31+
@Container
32+
@Neo4jServiceConnection
33+
static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:4.2");
34+
35+
@Test
36+
void myTest() {
37+
// ...
38+
}
39+
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.docs.howto.testing.testcontainers.serviceconnection
18+
19+
import org.junit.jupiter.api.Test
20+
import org.springframework.boot.test.autoconfigure.neo4j.Neo4jServiceConnection;
21+
import org.springframework.boot.test.context.SpringBootTest
22+
import org.springframework.test.context.DynamicPropertyRegistry
23+
import org.springframework.test.context.DynamicPropertySource
24+
import org.testcontainers.containers.Neo4jContainer
25+
import org.testcontainers.junit.jupiter.Container
26+
import org.testcontainers.junit.jupiter.Testcontainers
27+
28+
@SpringBootTest
29+
@Testcontainers
30+
internal class MyIntegrationTests {
31+
32+
@Test
33+
fun myTest() {
34+
// ...
35+
}
36+
37+
companion object {
38+
39+
@Container
40+
@Neo4jServiceConnection
41+
var neo4j: Neo4jContainer<*> = Neo4jContainer<Nothing>("neo4j:4.2")
42+
43+
}
44+
45+
}

spring-boot-project/spring-boot-test-autoconfigure/build.gradle

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,21 @@ dependencies {
6060
optional("org.apache.tomcat.embed:tomcat-embed-core")
6161
optional("org.mongodb:mongodb-driver-reactivestreams")
6262
optional("org.mongodb:mongodb-driver-sync")
63+
optional("org.testcontainers:cassandra")
64+
optional("org.testcontainers:couchbase")
65+
optional("org.testcontainers:elasticsearch")
66+
optional("org.testcontainers:influxdb")
67+
optional("org.testcontainers:jdbc")
68+
optional("org.testcontainers:kafka")
69+
optional("org.testcontainers:mariadb")
70+
optional("org.testcontainers:mongodb")
71+
optional("org.testcontainers:mssqlserver")
72+
optional("org.testcontainers:mysql")
73+
optional("org.testcontainers:neo4j")
74+
optional("org.testcontainers:postgresql")
75+
optional("org.testcontainers:rabbitmq")
76+
optional("org.testcontainers:r2dbc")
77+
optional("org.testcontainers:testcontainers")
6378
optional("io.micrometer:micrometer-tracing")
6479

6580
testImplementation(project(":spring-boot-project:spring-boot-actuator"))
@@ -85,6 +100,7 @@ dependencies {
85100
testImplementation("org.eclipse:yasson")
86101
testImplementation("org.hibernate.validator:hibernate-validator")
87102
testImplementation("org.hsqldb:hsqldb")
103+
testImplementation("org.influxdb:influxdb-java")
88104
testImplementation("org.jooq:jooq") {
89105
exclude group: "javax.xml.bind", module: "jaxb-api"
90106
}
@@ -95,15 +111,11 @@ dependencies {
95111
testImplementation("org.mockito:mockito-junit-jupiter")
96112
testImplementation("org.skyscreamer:jsonassert")
97113
testImplementation("org.springframework:spring-core-test")
114+
testImplementation("org.springframework.amqp:spring-rabbit")
98115
testImplementation("org.springframework.hateoas:spring-hateoas")
116+
testImplementation("org.springframework.kafka:spring-kafka")
99117
testImplementation("org.springframework.plugin:spring-plugin-core")
100-
testImplementation("org.testcontainers:cassandra")
101-
testImplementation("org.testcontainers:couchbase")
102-
testImplementation("org.testcontainers:elasticsearch")
103118
testImplementation("org.testcontainers:junit-jupiter")
104-
testImplementation("org.testcontainers:mongodb")
105-
testImplementation("org.testcontainers:neo4j")
106-
testImplementation("org.testcontainers:testcontainers")
107119
testImplementation("org.thymeleaf:thymeleaf")
108120
}
109121

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.test.autoconfigure.amqp;
18+
19+
import java.net.URI;
20+
import java.util.List;
21+
22+
import org.testcontainers.containers.RabbitMQContainer;
23+
24+
import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails;
25+
import org.springframework.boot.test.autoconfigure.service.connection.ContainerConnectionDetailsFactory;
26+
import org.springframework.boot.test.autoconfigure.service.connection.ContainerConnectionSource;
27+
28+
/**
29+
* {@link ContainerConnectionDetailsFactory} for
30+
* {@link RabbitServiceConnection @RabbitServiceConnection}-annotated
31+
* {@link RabbitMQContainer} fields.
32+
*
33+
* @author Moritz Halbritter
34+
* @author Andy Wilkinson
35+
* @author Phillip Webb
36+
*/
37+
class RabbitContainerConnectionDetailsFactory
38+
extends ContainerConnectionDetailsFactory<RabbitServiceConnection, RabbitConnectionDetails, RabbitMQContainer> {
39+
40+
@Override
41+
protected RabbitConnectionDetails getContainerConnectionDetails(
42+
ContainerConnectionSource<RabbitServiceConnection, RabbitConnectionDetails, RabbitMQContainer> source) {
43+
return new RabbitMqContainerConnectionDetails(source);
44+
}
45+
46+
/**
47+
* {@link RabbitConnectionDetails} backed by a {@link ContainerConnectionSource}.
48+
*/
49+
private static final class RabbitMqContainerConnectionDetails extends ContainerConnectionDetails
50+
implements RabbitConnectionDetails {
51+
52+
private final RabbitMQContainer container;
53+
54+
private RabbitMqContainerConnectionDetails(
55+
ContainerConnectionSource<RabbitServiceConnection, RabbitConnectionDetails, RabbitMQContainer> source) {
56+
super(source);
57+
this.container = source.getContainer();
58+
}
59+
60+
@Override
61+
public String getUsername() {
62+
return this.container.getAdminUsername();
63+
}
64+
65+
@Override
66+
public String getPassword() {
67+
return this.container.getAdminPassword();
68+
}
69+
70+
@Override
71+
public String getVirtualHost() {
72+
return null;
73+
}
74+
75+
@Override
76+
public List<Address> getAddresses() {
77+
URI uri = URI.create(this.container.getAmqpUrl());
78+
return List.of(new Address(uri.getHost(), uri.getPort()));
79+
}
80+
81+
}
82+
83+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.test.autoconfigure.amqp;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.springframework.boot.autoconfigure.amqp.RabbitConnectionDetails;
26+
import org.springframework.boot.test.autoconfigure.service.connection.ServiceConnection;
27+
28+
/**
29+
* Annotation that indicates that a field provides a RabbitMQ service connection.
30+
*
31+
* @author Moritz Halbritter
32+
* @author Andy Wilkinson
33+
* @author Phillip Webb
34+
* @since 3.1.0
35+
* @see RabbitConnectionDetails
36+
* @see ServiceConnection
37+
*/
38+
@Documented
39+
@Retention(RetentionPolicy.RUNTIME)
40+
@Target({ ElementType.FIELD, ElementType.TYPE })
41+
@ServiceConnection(RabbitConnectionDetails.class)
42+
public @interface RabbitServiceConnection {
43+
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Auto-configuration for using RabbitMQ in tests.
19+
*/
20+
package org.springframework.boot.test.autoconfigure.amqp;

0 commit comments

Comments
 (0)