I'm attempting to write ITs for a simple reactive service, but the test fails when attempt to obtain the test container (MongoDB Atlas Local) connection.
The error:
Caused by: .springframework.boot.autoconfigure.service.connection.ConnectionDetailsNotFoundException: No ConnectionDetails found for source '@ServiceConnection source for MovieServiceIT.mongoDBContainer'
Removing @ServiceConnection
yields:
.springframework.dao.DataAccessResourceFailureException: Timed out while waiting for a server that matches WritableServerSelector. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:27017, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {ioty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/[0:0:0:0:0:0:0:1]:27017}, caused by {java.ConnectException: Connection refused}}]
Here is MovieServiceIT
:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
@ExtendWith(SpringExtension.class)
@AutoConfigureWebTestClient
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@DisplayName("Testing Movies API")
class MovieServiceIT {
@Container
@ServiceConnection
static MongoDBAtlasLocalContainer mongoDBContainer = new MongoDBAtlasLocalContainer("mongodb/mongodb-atlas-local:8.0.3");
@Autowired
private WebTestClient webTestClient;
@Autowired
private MoviesRepository moviesRepository;
@BeforeEach
void setUp() throws IOException {
assertThat(mongoDBContainer.isCreated()).isTrue();
assertThat(mongoDBContainer.isRunning()).isTrue();
movies = DataLoadUtil.loadTestModels("resources/data/movies.json"); // This works
moviesRepository.deleteAll()
.thenMany(
Flux.just(movies.toArray(new Movie[]{})).flatMap(moviesRepository::save)
)
.blockLast();
}
@Test
@Order(1)
@DisplayName("Endpoint: GET /movies/{id}")
void getMovieById() throws Exception {
webTestClient.get()
.uri("/movies/573a1392f29313caabcdae3d")
.exchange()
.expectStatus().isOk()
.expectBody(Movie.class)
.consumeWith(response -> {
assertEquals("Modern Times", Objects.requireNonNull(response.getResponseBody()).getTitle());
});
}
}
Here is most of the pom.xml
:
<dependencies>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-otlp</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- TESTING //-->
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>.testcontainers</groupId>
<artifactId>mongodb</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-docker-compose</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>.springframework.restdocs</groupId>
<artifactId>spring-restdocs-webtestclient</artifactId>
<scope>test</scope>
</dependency>
<!-- BUILD //-->
<dependency>
<groupId>.springdoc</groupId>
<artifactId>springdoc-openapi-starter-common</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-models</artifactId>
<version>2.2.9</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>generate-docs</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>html</backend>
<doctype>book</doctype>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>.springframework.restdocs</groupId>
<artifactId>spring-restdocs-asciidoctor</artifactId>
<version>3.0.1</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<dependencies>
<dependency>
<groupId>me.fabriciorby</groupId>
<artifactId>maven-surefire-junit5-tree-reporter</artifactId>
<version>1.3.0</version>
</dependency>
</dependencies>
<configuration>
<reportFormat>plain</reportFormat>
<systemPropertyVariables>
<java.util.logging.manager>.apache.logging.log4j.jul.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
<statelessTestsetReporter implementation=".apache.maven.plugin.surefire.extensions.junit5.JUnit5Xml30StatelessReporter">
<disable>false</disable>
<version>3.0</version>
<usePhrasedFileName>false</usePhrasedFileName>
<usePhrasedTestSuiteClassName>true</usePhrasedTestSuiteClassName>
<usePhrasedTestCaseClassName>true</usePhrasedTestCaseClassName>
<usePhrasedTestCaseMethodName>true</usePhrasedTestCaseMethodName>
</statelessTestsetReporter>
<consoleOutputReporter implementation=".apache.maven.plugin.surefire.extensions.junit5.JUnit5ConsoleOutputReporter">
<disable>true</disable>
</consoleOutputReporter>
<statelessTestsetInfoReporter implementation=".apache.maven.plugin.surefire.extensions.junit5.JUnit5StatelessTestsetInfoTreeReporter">
<disable>false</disable>
<usePhrasedFileName>false</usePhrasedFileName>
<usePhrasedClassNameInRunning>true</usePhrasedClassNameInRunning>
<usePhrasedClassNameInTestCaseSummary>true</usePhrasedClassNameInTestCaseSummary>
</statelessTestsetInfoReporter>
</configuration>
</plugin>
</plugins>
</build>
I'm attempting to write ITs for a simple reactive service, but the test fails when attempt to obtain the test container (MongoDB Atlas Local) connection.
The error:
Caused by: .springframework.boot.autoconfigure.service.connection.ConnectionDetailsNotFoundException: No ConnectionDetails found for source '@ServiceConnection source for MovieServiceIT.mongoDBContainer'
Removing @ServiceConnection
yields:
.springframework.dao.DataAccessResourceFailureException: Timed out while waiting for a server that matches WritableServerSelector. Client view of cluster state is {type=UNKNOWN, servers=[{address=localhost:27017, type=UNKNOWN, state=CONNECTING, exception={com.mongodb.MongoSocketOpenException: Exception opening socket}, caused by {ioty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/[0:0:0:0:0:0:0:1]:27017}, caused by {java.ConnectException: Connection refused}}]
Here is MovieServiceIT
:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
@ExtendWith(SpringExtension.class)
@AutoConfigureWebTestClient
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@DisplayName("Testing Movies API")
class MovieServiceIT {
@Container
@ServiceConnection
static MongoDBAtlasLocalContainer mongoDBContainer = new MongoDBAtlasLocalContainer("mongodb/mongodb-atlas-local:8.0.3");
@Autowired
private WebTestClient webTestClient;
@Autowired
private MoviesRepository moviesRepository;
@BeforeEach
void setUp() throws IOException {
assertThat(mongoDBContainer.isCreated()).isTrue();
assertThat(mongoDBContainer.isRunning()).isTrue();
movies = DataLoadUtil.loadTestModels("resources/data/movies.json"); // This works
moviesRepository.deleteAll()
.thenMany(
Flux.just(movies.toArray(new Movie[]{})).flatMap(moviesRepository::save)
)
.blockLast();
}
@Test
@Order(1)
@DisplayName("Endpoint: GET /movies/{id}")
void getMovieById() throws Exception {
webTestClient.get()
.uri("/movies/573a1392f29313caabcdae3d")
.exchange()
.expectStatus().isOk()
.expectBody(Movie.class)
.consumeWith(response -> {
assertEquals("Modern Times", Objects.requireNonNull(response.getResponseBody()).getTitle());
});
}
}
Here is most of the pom.xml
:
<dependencies>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-otlp</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- TESTING //-->
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>.testcontainers</groupId>
<artifactId>mongodb</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-docker-compose</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>.springframework.restdocs</groupId>
<artifactId>spring-restdocs-webtestclient</artifactId>
<scope>test</scope>
</dependency>
<!-- BUILD //-->
<dependency>
<groupId>.springdoc</groupId>
<artifactId>springdoc-openapi-starter-common</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-models</artifactId>
<version>2.2.9</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>generate-docs</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>html</backend>
<doctype>book</doctype>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>.springframework.restdocs</groupId>
<artifactId>spring-restdocs-asciidoctor</artifactId>
<version>3.0.1</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<dependencies>
<dependency>
<groupId>me.fabriciorby</groupId>
<artifactId>maven-surefire-junit5-tree-reporter</artifactId>
<version>1.3.0</version>
</dependency>
</dependencies>
<configuration>
<reportFormat>plain</reportFormat>
<systemPropertyVariables>
<java.util.logging.manager>.apache.logging.log4j.jul.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
<statelessTestsetReporter implementation=".apache.maven.plugin.surefire.extensions.junit5.JUnit5Xml30StatelessReporter">
<disable>false</disable>
<version>3.0</version>
<usePhrasedFileName>false</usePhrasedFileName>
<usePhrasedTestSuiteClassName>true</usePhrasedTestSuiteClassName>
<usePhrasedTestCaseClassName>true</usePhrasedTestCaseClassName>
<usePhrasedTestCaseMethodName>true</usePhrasedTestCaseMethodName>
</statelessTestsetReporter>
<consoleOutputReporter implementation=".apache.maven.plugin.surefire.extensions.junit5.JUnit5ConsoleOutputReporter">
<disable>true</disable>
</consoleOutputReporter>
<statelessTestsetInfoReporter implementation=".apache.maven.plugin.surefire.extensions.junit5.JUnit5StatelessTestsetInfoTreeReporter">
<disable>false</disable>
<usePhrasedFileName>false</usePhrasedFileName>
<usePhrasedClassNameInRunning>true</usePhrasedClassNameInRunning>
<usePhrasedClassNameInTestCaseSummary>true</usePhrasedClassNameInTestCaseSummary>
</statelessTestsetInfoReporter>
</configuration>
</plugin>
</plugins>
</build>
Share
Improve this question
edited yesterday
John Manko
asked yesterday
John MankoJohn Manko
1,9081 gold badge28 silver badges57 bronze badges
1 Answer
Reset to default 0Looking at the Testcontainers
page in the spring documentation, there is no mention of ConnectionDetails
being provided for the MongoDBAtlasLocalContainer
.
So it looks like we need to provide a custom implementation of ConnectionDetails
.
To implement ConnectionDetails for MongoDBAtlasLocalContainer myself, I referenced the test code implemented in Spring AI.
Steps to Implement ConnectionDetails for MongoDBAtlasLocalContainer
1. Create a MongoDbAtlasLocalContainerConnectionDetailsFactory
Below is an example implementation inspired by the Spring AI project (The code below can be found at the link).
class MongoDbAtlasLocalContainerConnectionDetailsFactory
extends ContainerConnectionDetailsFactory<MongoDBAtlasLocalContainer, MongoConnectionDetails> {
@Override
protected MongoConnectionDetails getContainerConnectionDetails(
ContainerConnectionSource<MongoDBAtlasLocalContainer> source) {
return new MongoDbAtlasLocalContainerConnectionDetails(source);
}
/**
* {@link MongoConnectionDetails} backed by a {@link ContainerConnectionSource}.
*/
private static final class MongoDbAtlasLocalContainerConnectionDetails
extends ContainerConnectionDetails<MongoDBAtlasLocalContainer> implements MongoConnectionDetails {
private MongoDbAtlasLocalContainerConnectionDetails(
ContainerConnectionSource<MongoDBAtlasLocalContainer> source) {
super(source);
}
@Override
public ConnectionString getConnectionString() {
return new ConnectionString(getContainer().getConnectionString());
}
}
}
2. Register the ConnectionDetailsFactory in spring.factories
Create a spring.factories
file in /resources/META-INF/spring.factories
and enter the following contents
.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\
.example.demo.MongoDbAtlasLocalContainerConnectionDetailsFactory
Replace .example.demo
with the actual package name where your factory exists
3. Proceed with the test.
Here's an example test to verify the test container works:
@Testcontainers
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MovieServiceIT {
@Container
static MongoDBAtlasLocalContainer mongoDBContainer = new MongoDBAtlasLocalContainer("mongodb/mongodb-atlas-local:8.0.3");
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withPropertyValues("spring.data.mongodb.database=springsample",
"spring.data.mongodb.uri=" + mongoDBContainer.getConnectionString());
@Test
void test() {
this.contextRunner.run(context -> {
assertThat(mongoDBContainer.isCreated()).isTrue();
assertThat(mongoDBContainer.isRunning()).isTrue();
});
}
}