Reputation: 1908
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: org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsNotFoundException: No ConnectionDetails found for source '@ServiceConnection source for MovieServiceIT.mongoDBContainer'
Removing @ServiceConnection
yields:
org.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 {io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/[0:0:0:0:0:0:0:1]:27017}, caused by {java.net.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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- TESTING //-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.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>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mongodb</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-docker-compose</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-webtestclient</artifactId>
<scope>test</scope>
</dependency>
<!-- BUILD //-->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-common</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.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>org.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>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.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>org.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>org.apache.logging.log4j.jul.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
<statelessTestsetReporter implementation="org.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="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5ConsoleOutputReporter">
<disable>true</disable>
</consoleOutputReporter>
<statelessTestsetInfoReporter implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5StatelessTestsetInfoTreeReporter">
<disable>false</disable>
<usePhrasedFileName>false</usePhrasedFileName>
<usePhrasedClassNameInRunning>true</usePhrasedClassNameInRunning>
<usePhrasedClassNameInTestCaseSummary>true</usePhrasedClassNameInTestCaseSummary>
</statelessTestsetInfoReporter>
</configuration>
</plugin>
</plugins>
</build>
Upvotes: 0
Views: 48
Reputation: 31
Looking 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.
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());
}
}
}
Create a spring.factories
file in /resources/META-INF/spring.factories
and enter the following contents
org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\
org.example.demo.MongoDbAtlasLocalContainerConnectionDetailsFactory
Replace org.example.demo
with the actual package name where your factory exists
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();
});
}
}
Upvotes: 0