I have put together a minimal example that demonstrates how the use of the @ServiceConnection annotation with Spring Boot 3.1.2 and testcontainers breaks native tests.

When running ./gradlew nativeTest, I get the following error:

org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsNotFoundException: No ConnectionDetails found for source '@ServiceConnection source for MongoDbTestBase.database'

To reproduce the problem, run the following commands:

git clone https://github.com/magnus-larsson/sb312-tc-native-bug.git
cd sb312-tc-native-bug
./gradlew nativeTest

Note: Running ./gradlew test works as expected.

To workaround the problem, do the following:

  1. Edit src/test/java/se/magnus/microservices/core/product/MongoDbTestBase.java
  2. Comment out @ServiceConnection
  3. Remove comments from the method setProperties that is annotated with @DynamicPropertySource

With these changes in place, both ./gradlew test and ./gradlew nativeTest works as expected.

Comment From: philwebb

Could you please provide a link to the reproducer?

EDIT: Never mind, @wilkinsona just pointed out the git clone...

Comment From: magnus-larsson

The git repo is available at https://github.com/magnus-larsson/sb312-tc-native-bug.

I noted that the issue is marked with status:waiting-for-feedback, is there any more info required from my side?

Comment From: mhalbritter

Hey @magnus-larsson, sorry for taking my time to look at this issue. Thanks for the report and for the reproducer. You don't need to provide more information, we have everything we need.

I can reproduce this. The 3 registrations are returned in org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactories#getConnectionDetails:

List<Registration<S, ?>> registrations = getRegistrations(source, required);

but this call always returns null in a native image:

ConnectionDetails connectionDetails = registration.factory().getConnectionDetails(source);

On the JVM this works for the MongoContainerConnectionDetailsFactory.

Comment From: mhalbritter

The MongoContainerConnectionDetailsFactory checks for the presence of the com.mongodb.ConnectionString class, which is not registered for reflection in a native image, and this check returns false.

Comment From: mhalbritter

Until we fix this, you can use this workaround:

@ImportRuntimeHints(MyHints.class)
public abstract class MongoDbTestBase {

  // ... snip ...

    static class MyHints implements RuntimeHintsRegistrar {
        @Override
        public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
            hints.reflection().registerType(TypeReference.of("com.mongodb.ConnectionString"));
        }
    }

}

Comment From: magnus-larsson

It looks like Spring Boot 3.1.4 solved this issue.

I don't need the workaround described above to make nativeTests work after upgrading to Spring Boot 3.1.4.

Comment From: wilkinsona

Thanks for the update, @magnus-larsson.

I'm surprised that it works for you with Spring boot 3.1.4. As far as I can tell, nothing is generating the reflection hint for com.mongodb.ConnectionString so it shouldn't be available through reflection in a native image.

Updating sb312-tc-native-bug to Spring Boot 3.1.4 and running ./gradlew nativeTest seems to confirm this. All 12 tests fail with exceptions similar to this:

Caused by: org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsNotFoundException: No ConnectionDetails found for source '@ServiceConnection source for MongoDbTestBase.database'
        at org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactories.getConnectionDetails(ConnectionDetailsFactories.java:89)
        at org.springframework.boot.testcontainers.service.connection.ConnectionDetailsRegistrar.registerBeanDefinitions(ConnectionDetailsRegistrar.java:71)
        at org.springframework.boot.testcontainers.service.connection.ConnectionDetailsRegistrar.lambda$registerBeanDefinitions$0(ConnectionDetailsRegistrar.java:66)
        at java.base@17.0.8/java.util.ArrayList.forEach(ArrayList.java:1511)
        at org.springframework.boot.testcontainers.service.connection.ConnectionDetailsRegistrar.registerBeanDefinitions(ConnectionDetailsRegistrar.java:66)
        at org.springframework.boot.testcontainers.service.connection.ServiceConnectionContextCustomizer.customizeContext(ServiceConnectionContextCustomizer.java:66)
        at org.springframework.boot.test.context.SpringBootContextLoader$ContextCustomizerAdapter.initialize(SpringBootContextLoader.java:435)
        at org.springframework.boot.SpringApplication.applyInitializers(SpringApplication.java:610)
        at org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:390)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:314)
        at org.springframework.boot.test.context.SpringBootContextLoader.lambda$loadContext$3(SpringBootContextLoader.java:137)
        at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:58)
        at org.springframework.util.function.ThrowingSupplier.get(ThrowingSupplier.java:46)
        at org.springframework.boot.SpringApplication.withHook(SpringApplication.java:1409)
        at org.springframework.boot.test.context.SpringBootContextLoader$ContextLoaderHook.run(SpringBootContextLoader.java:545)
        at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:137)
        at org.springframework.boot.test.context.SpringBootContextLoader.loadContextForAotRuntime(SpringBootContextLoader.java:119)
        at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInAotMode(DefaultCacheAwareContextLoaderDelegate.java:217)
        at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116)
        ... 68 common frames omitted

I think we still need to contribute a hint here.

Comment From: wilkinsona

Looking at the code, I think the factories for Neo4j, Redis, and Zipkin as well as the various different R2DBC factories are also affected.

Comment From: magnus-larsson

@wilkinsona: You are right; I tested it on another project. Native tests still fail in the sb312-tc-native-bug project when using Spring Boot 3.1.4!

Comment From: wilkinsona

I think this has caused #38392.

Comment From: philwebb

The fix for #38392 was merged in f68df82b30a9d73b629c62c66e22956c64eaa73e