Overview

When running various bean override tests in AOT mode (via AotIntegrationTests), AOT processing fails with a stack trace similar to the following.

org.springframework.test.context.aot.TestContextAotException: Failed to generate AOT artifacts for test classes [org.springframework.test.context.bean.override.convention.TestBeanByTypeIntegrationTests]
    at org.springframework.test.context.aot.TestContextAotGenerator.lambda$10(TestContextAotGenerator.java:281)
    at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:721)
    at org.springframework.util.MultiValueMapAdapter.forEach(MultiValueMapAdapter.java:179)
    at org.springframework.test.context.aot.TestContextAotGenerator.processAheadOfTime(TestContextAotGenerator.java:244)
    at org.springframework.test.context.aot.TestContextAotGenerator.processAheadOfTime(TestContextAotGenerator.java:205)
    at org.springframework.test.context.aot.AotIntegrationTests.runEndToEndTests(AotIntegrationTests.java:166)
    at org.springframework.test.context.aot.AotIntegrationTests.endToEndTestsForSelectedTestClasses(AotIntegrationTests.java:159)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.springframework.core.test.tools.CompileWithForkedClassLoaderExtension.intercept(CompileWithForkedClassLoaderExtension.java:97)
    at org.springframework.core.test.tools.CompileWithForkedClassLoaderExtension.interceptTestMethod(CompileWithForkedClassLoaderExtension.java:83)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: org.springframework.test.context.aot.TestContextAotException: Failed to process test class [org.springframework.test.context.bean.override.convention.TestBeanByTypeIntegrationTests] for AOT
    at org.springframework.test.context.aot.TestContextAotGenerator.processAheadOfTime(TestContextAotGenerator.java:317)
    at org.springframework.test.context.aot.TestContextAotGenerator.lambda$10(TestContextAotGenerator.java:273)
    ... 11 more
Caused by: java.lang.IllegalStateException: No constructor or factory method candidate found for Root bean: class [null]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null and argument types []
    at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorOrFactoryMethod(ConstructorResolver.java:996)
    at org.springframework.beans.factory.support.RegisteredBean.resolveConstructorOrFactoryMethod(RegisteredBean.java:218)
    at org.springframework.beans.factory.support.RegisteredBean.resolveInstantiationDescriptor(RegisteredBean.java:228)
    at org.springframework.util.function.SingletonSupplier.get(SingletonSupplier.java:106)
    at org.springframework.beans.factory.aot.DefaultBeanRegistrationCodeFragments.getTarget(DefaultBeanRegistrationCodeFragments.java:86)
    at org.springframework.beans.factory.aot.BeanDefinitionMethodGenerator.generateBeanDefinitionMethod(BeanDefinitionMethodGenerator.java:85)
    at org.springframework.beans.factory.aot.BeanRegistrationsAotContribution.lambda$3(BeanRegistrationsAotContribution.java:89)
    at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:721)
    at org.springframework.beans.factory.aot.BeanRegistrationsAotContribution.generateRegisterBeanDefinitionsMethod(BeanRegistrationsAotContribution.java:87)
    at org.springframework.beans.factory.aot.BeanRegistrationsAotContribution.lambda$1(BeanRegistrationsAotContribution.java:72)
    at org.springframework.aot.generate.GeneratedMethod.<init>(GeneratedMethod.java:54)
    at org.springframework.aot.generate.GeneratedMethods.add(GeneratedMethods.java:112)
    at org.springframework.aot.generate.GeneratedMethods.add(GeneratedMethods.java:89)
    at org.springframework.beans.factory.aot.BeanRegistrationsAotContribution.applyTo(BeanRegistrationsAotContribution.java:71)
    at org.springframework.context.aot.BeanFactoryInitializationAotContributions.applyTo(BeanFactoryInitializationAotContributions.java:78)
    at org.springframework.context.aot.ApplicationContextAotGenerator.lambda$0(ApplicationContextAotGenerator.java:58)
    at org.springframework.context.aot.ApplicationContextAotGenerator.withCglibClassHandler(ApplicationContextAotGenerator.java:67)
    at org.springframework.context.aot.ApplicationContextAotGenerator.processAheadOfTime(ApplicationContextAotGenerator.java:53)
    at org.springframework.test.context.aot.TestContextAotGenerator.processAheadOfTime(TestContextAotGenerator.java:314)
    ... 12 more

The key part is this:

No constructor or factory method candidate found for Root bean: class [null]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null and argument types []

... which seems like a relatively empty BeanDefinition.

Failing Test Classes

  • TestBeanByTypeIntegrationTests
  • TestBeanInheritanceIntegrationTests$ConcreteTestBeanIntegrationTests
  • MockitoBeanByTypeIntegrationTests
  • MockitoBeanIntegrationTests

Related Issues

  • https://github.com/spring-projects/spring-boot/issues/32195
  • 29122

  • 32932

Comment From: snicoll

Quoting #32933:

Although @MockitoBean and @MockitoSpyBean will not work out-of-the-box within a GraalVM native image due to their dependency on Mockito, the general Bean Override support for tests should ideally work within a native image.

That is partially accurate. The mockito support in Spring Boot never worked in AOT mode on the JVM. It current fails as follows:

Caused by: org.springframework.aot.generate.UnsupportedTypeValueCodeGenerationException: Code generation does not support org.springframework.boot.test.mock.mockito.MockDefinition
    at org.springframework.aot.generate.ValueCodeGenerator.generateCode(ValueCodeGenerator.java:113)
    ... 42 more

That is because the TCF requires the key for a context to be computed at a certain time and makes it very AOT unfriendly as a result. I think this issue needs to be requalified as we need to fix that.

Next up, the support works by registering a singleton with a "dummy" bean definition and that's why this is failing.

Comment From: snicoll

This issue is no longer relevant as it's obvious that bean overriding does not work with AOT. And we already have an issue to investigate how to fix that, see https://github.com/spring-projects/spring-framework/issues/32933