Skip to content

The new support for testcontainers in Spring Boot 3.1.0 does not work with native tests #35663

Closed

Description

@magnus-larsson

I have created a Github repo that can be used to reproduce the error: https://github.com/magnus-larsson/sb31-nativetest-demo.

The sample code contains a test that uses the new support for testcontainers together with Postgresql.

Running tests that use testcontainers for Postgresql works fine:

./gradlew clean test

Building a jar file and a native image and running them with a Postregsql db in Docker also works fine:

docker-compose up -d
export SPRING_DATASOURCE_URL=jdbc:postgresql://localhost/bp
export SPRING_DATASOURCE_USERNAME=bp
export SPRING_DATASOURCE_PASSWORD=bp

./gradlew build
java -jar build/libs/tcdemo-0.0.1-SNAPSHOT.jar
curl localhost:8080/customers
CTRL/C

./gradlew nativeBuild
java -jar build/libs/tcdemo-0.0.1-SNAPSHOT.jar
curl localhost:8080/customers
CTRL/C

docker-compose down

But, when running native tests, they fail:

./gradlew clean nativeTest

Error message:

Failures (1):
  JUnit Jupiter:TcdemoApplicationTests
    ClassSource [className = 'com.example.tcdemo.TcdemoApplicationTests', filePosition = null]
    => java.lang.ExceptionInInitializerError
       org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:113)
       org.junit.jupiter.engine.execution.ExtensionValuesStore.lambda$getOrComputeIfAbsent$4(ExtensionValuesStore.java:86)
       org.junit.jupiter.engine.execution.ExtensionValuesStore$MemoizingSupplier.computeValue(ExtensionValuesStore.java:223)
       org.junit.jupiter.engine.execution.ExtensionValuesStore$MemoizingSupplier.get(ExtensionValuesStore.java:211)
       org.junit.jupiter.engine.execution.ExtensionValuesStore$StoredValue.evaluate(ExtensionValuesStore.java:191)
       [...]
       Suppressed: java.lang.NoClassDefFoundError: Could not initialize class org.springframework.test.context.BootstrapUtils
         org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:113)
         org.junit.jupiter.engine.execution.ExtensionValuesStore.lambda$getOrComputeIfAbsent$4(ExtensionValuesStore.java:86)
         org.junit.jupiter.engine.execution.ExtensionValuesStore$MemoizingSupplier.computeValue(ExtensionValuesStore.java:223)
         org.junit.jupiter.engine.execution.ExtensionValuesStore$MemoizingSupplier.get(ExtensionValuesStore.java:211)
         org.junit.jupiter.engine.execution.ExtensionValuesStore$StoredValue.evaluate(ExtensionValuesStore.java:191)
         [...]
     Caused by: java.lang.IllegalStateException: Failed to load class for @org.springframework.test.context.web.WebAppConfiguration
       org.springframework.test.context.BootstrapUtils.loadWebAppConfigurationClass(BootstrapUtils.java:213)
       org.springframework.test.context.BootstrapUtils.<clinit>(BootstrapUtils.java:63)
       [...]
     Caused by: java.lang.ClassNotFoundException: org.springframework.test.context.web.WebAppConfiguration
       [email protected]/java.lang.Class.forName(DynamicHub.java:1132)
       org.springframework.util.ClassUtils.forName(ClassUtils.java:284)
       org.springframework.test.context.BootstrapUtils.loadWebAppConfigurationClass(BootstrapUtils.java:209)
       [...]

Activity

  1. 1713612859 commented on May 28, 2023

    @1713612859

    please check yml.properties

  2. wilkinsona commented on May 30, 2023

    @wilkinsona
    Member

    AOT processing of TcdemoApplicationTests fails:

    2023-05-30T09:49:18.719+01:00  WARN 52482 --- [           main] o.s.t.c.aot.TestContextAotGenerator      : Failed to generate AOT artifacts for test classes [com.example.tcdemo.TcdemoApplicationTests]
    
    org.springframework.test.context.aot.TestContextAotException: Failed to process test class [com.example.tcdemo.TcdemoApplicationTests] for AOT
            at org.springframework.test.context.aot.TestContextAotGenerator.processAheadOfTime(TestContextAotGenerator.java:238) ~[spring-test-6.0.9.jar:6.0.9]
            at org.springframework.test.context.aot.TestContextAotGenerator.lambda$processAheadOfTime$4(TestContextAotGenerator.java:204) ~[spring-test-6.0.9.jar:6.0.9]
            at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:721) ~[na:na]
            at org.springframework.util.MultiValueMapAdapter.forEach(MultiValueMapAdapter.java:179) ~[spring-core-6.0.9.jar:6.0.9]
            at org.springframework.test.context.aot.TestContextAotGenerator.processAheadOfTime(TestContextAotGenerator.java:196) ~[spring-test-6.0.9.jar:6.0.9]
            at org.springframework.test.context.aot.TestContextAotGenerator.processAheadOfTime(TestContextAotGenerator.java:158) ~[spring-test-6.0.9.jar:6.0.9]
            at org.springframework.test.context.aot.TestAotProcessor.performAotProcessing(TestAotProcessor.java:91) ~[spring-test-6.0.9.jar:6.0.9]
            at org.springframework.test.context.aot.TestAotProcessor.doProcess(TestAotProcessor.java:72) ~[spring-test-6.0.9.jar:6.0.9]
            at org.springframework.test.context.aot.TestAotProcessor.doProcess(TestAotProcessor.java:39) ~[spring-test-6.0.9.jar:6.0.9]
            at org.springframework.context.aot.AbstractAotProcessor.process(AbstractAotProcessor.java:82) ~[spring-context-6.0.9.jar:6.0.9]
            at org.springframework.boot.test.context.SpringBootTestAotProcessor.main(SpringBootTestAotProcessor.java:63) ~[spring-boot-test-3.1.0.jar:3.1.0]
    Caused by: java.lang.IllegalArgumentException: Code generation is not supported for bean definitions declaring an instance supplier callback : Root bean: class [org.springframework.boot.testcontainers.service.connection.jdbc.JdbcContainerConnectionDetailsFactory$JdbcContainerConnectionDetails]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null
            at org.springframework.beans.factory.aot.BeanDefinitionMethodGenerator.<init>(BeanDefinitionMethodGenerator.java:82) ~[spring-beans-6.0.9.jar:6.0.9]
            at org.springframework.beans.factory.aot.BeanDefinitionMethodGeneratorFactory.getBeanDefinitionMethodGenerator(BeanDefinitionMethodGeneratorFactory.java:100) ~[spring-beans-6.0.9.jar:6.0.9]
            at org.springframework.beans.factory.aot.BeanDefinitionMethodGeneratorFactory.getBeanDefinitionMethodGenerator(BeanDefinitionMethodGeneratorFactory.java:115) ~[spring-beans-6.0.9.jar:6.0.9]
            at org.springframework.beans.factory.aot.BeanRegistrationsAotProcessor.processAheadOfTime(BeanRegistrationsAotProcessor.java:49) ~[spring-beans-6.0.9.jar:6.0.9]
            at org.springframework.beans.factory.aot.BeanRegistrationsAotProcessor.processAheadOfTime(BeanRegistrationsAotProcessor.java:37) ~[spring-beans-6.0.9.jar:6.0.9]
            at org.springframework.context.aot.BeanFactoryInitializationAotContributions.getContributions(BeanFactoryInitializationAotContributions.java:67) ~[spring-context-6.0.9.jar:6.0.9]
            at org.springframework.context.aot.BeanFactoryInitializationAotContributions.<init>(BeanFactoryInitializationAotContributions.java:49) ~[spring-context-6.0.9.jar:6.0.9]
            at org.springframework.context.aot.BeanFactoryInitializationAotContributions.<init>(BeanFactoryInitializationAotContributions.java:44) ~[spring-context-6.0.9.jar:6.0.9]
            at org.springframework.context.aot.ApplicationContextAotGenerator.lambda$processAheadOfTime$0(ApplicationContextAotGenerator.java:58) ~[spring-context-6.0.9.jar:6.0.9]
            at org.springframework.context.aot.ApplicationContextAotGenerator.withCglibClassHandler(ApplicationContextAotGenerator.java:67) ~[spring-context-6.0.9.jar:6.0.9]
            at org.springframework.context.aot.ApplicationContextAotGenerator.processAheadOfTime(ApplicationContextAotGenerator.java:53) ~[spring-context-6.0.9.jar:6.0.9]
            at org.springframework.test.context.aot.TestContextAotGenerator.processAheadOfTime(TestContextAotGenerator.java:234) ~[spring-test-6.0.9.jar:6.0.9]
            ... 10 common frames omitted
    

    This leads to incomplete runtime hints and the subsequent ClassNotFoundException: org.springframework.test.context.web.WebAppConfiguration.

    @sbrannen I wonder if AOT processing of tests should fail fast in this situation? If AOT processing has failed, running the tests is very unlikely to succeed and it's easy to miss the earlier error, particularly when the subsequent failure is apparently unrelated.

  3. wilkinsona commented on May 30, 2023

    @wilkinsona
    Member

    With a local change in place to work around the AOT processing failure, Testcontainers itself does not work with native tests. They fail when trying to bind ~/.docker/config.json into a DockerConfigFile instance:

    Caused by: org.testcontainers.shaded.com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of org.testcontainers.shaded.com.github.dockerjava.core.DockerConfigFile: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
     at [Source: /Users/awilkinson/.docker/config.json; line: 2, column: 2]
           org.testcontainers.shaded.com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:1456)
           org.testcontainers.shaded.com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1012)
           org.testcontainers.shaded.com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1206)
           org.testcontainers.shaded.com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:314)
           org.testcontainers.shaded.com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:148)
           [...]
    

    There is reachability metadata for it but it appears to be incomplete. The metadata is for Testcontainers 1.17.6 and the above failure occurs with 1.18.0 so the problem may be have been introduced in Testcontainers 1.18 or the tests for the reachability metadata may not drive this code path.

    With the AOT processing workaround in place, nativeTest succeeds with the following additional reflection config:

      {
        "name": "org.testcontainers.shaded.com.github.dockerjava.core.DockerConfigFile",
        "allDeclaredMethods": true,
        "allDeclaredConstructors": true
      }
  4. wilkinsona commented on May 30, 2023

    @wilkinsona
    Member

    I've opened oracle/graalvm-reachability-metadata#301 to update the reachability metadata so that ~/.docker/config.json can be deserialised into a DockerConfigFile instance.

  5. magnus-larsson commented on Jun 2, 2023

    @magnus-larsson
    Author

    Hello @wilkinsona, and thanks for your support! I noticed that your PR to the reachability project has been approved and merged, great!

    How can I test if your PR helps with the problem reported in this issue?

  6. wilkinsona commented on Jun 2, 2023

    @wilkinsona
    Member

    You can't I'm afraid. We need to make a change in Boot before you'll reach the point where the Testcontainers problem occurs.

  7. magnus-larsson commented on Jun 2, 2023

    @magnus-larsson
    Author

    Ok, thanks for the update!

    Any tentative ideas for what Spring Boot version will get the change implemented?

  8. self-assigned this
    on Jun 6, 2023
  9. modified the milestones: 3.1.x, 3.1.1 on Jun 6, 2023
  10. magnus-larsson commented on Jun 7, 2023

    @magnus-larsson
    Author

    Hello @wilkinsona, and thanks for the bug fix!

    I tried it out using Spring Boot 3.1.1-SNAPSHOT.

    Now I get the error message you referred to above:

    Caused by: org.testcontainers.shaded.com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of org.testcontainers.shaded.com.github.dockerjava.core.DockerConfigFile: no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?)
     at [Source: /Users/magnus/.docker/config.json; line: 2, column: 3]
    

    Will oracle/graalvm-reachability-metadata#301 resolve this or what needs to be done before the ./gradlew clean nativeTest works with my sample code?

  11. wilkinsona commented on Jun 7, 2023

    @wilkinsona
    Member

    Yes, oracle/graalvm-reachability-metadata#301 should resolve this. Until the reachability metadata is released, you could provide similar hints yourself:

    	static class TestcontainersRuntimeHints implements RuntimeHintsRegistrar {
    
    		@Override
    		public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
    			hints.reflection().registerType(DockerConfigFile.class, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
    					MemberCategory.INVOKE_DECLARED_METHODS);
    		}
    		
    	}

    This registrar can then be imported by your test class:

    @ImportRuntimeHints(TestcontainersRuntimeHints.class)
    class TcdemoApplicationTests {
    …
  12. added a commit that references this issue on Jul 23, 2026
    bd2fff1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions