Issue
Starting from Spring-Boot 3.2.x the @SpyBean is not able to initialize MongoRepository bean of the generic type. Please check the example project: https://github.com/pavlo-reshetniak/spybean-issue-example
How to reproduce
- Make sure the spring-boot-starter-parent has version 3.2.x (e.g. 3.2.4) in the parent POM file
- Run unit test: com.example.service.ExampleServiceTest#shouldExtractDataFromMongo
Result
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.repository.ExampleRepository]: Specified class is an interface
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:77)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1311)
... 39 more
Possible workaround
- Downgrading the spring-boot-starter-parent to version 3.1.10 will fix the issue
- Removing the generic type under the @SpyBean initialization will fix the issue:
From this:
@SpyBean
private ExampleRepository<ExampleData> exampleRepository;
to this:
@SpyBean
private ExampleRepository exampleRepository;
Comment From: wilkinsona
The change in behavior is due to https://github.com/spring-projects/spring-data-commons/commit/98f20a4457ace08348a8e568eaed2c451e127699. The move away from the FactoryBean.OBJECT_TYPE_ATTRIBUTE to setting the definition's target type means that MockitoPostProcessor now fails to match the generic signature of the spied field:
https://github.com/spring-projects/spring-boot/blob/a3d62e0b2fef26f7fb06781b6a3bf1b5f7b48a07/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessor.java#L258-L261
This failed matching means that we don't find the existing bean and instantiating one fails as it's an interface.
It's unfortunate that Framework doesn't find the factory bean when we ask for beans of a particular type:
https://github.com/spring-projects/spring-boot/blob/a3d62e0b2fef26f7fb06781b6a3bf1b5f7b48a07/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoPostProcessor.java#L252-L253
It doesn't find them as we are not permitting eager init so it only checks raw factory beans and not the beans created by those factory beans. Perhaps this could be improved when the bean definition has additional type information as it does in this case?
In the absence of a change in Framework, when the object type attribute's is null, I think we need to fall back to the target type information that I think we can access through getResolvableType().
Comment From: wilkinsona
I've opened https://github.com/spring-projects/spring-framework/issues/32649 to track a change on the Framework side that will hopefully get this working without changing anything in Boot.
Comment From: quaff
FYI, there are some improvements about repository definition's target type: https://github.com/spring-projects/spring-data-commons/commit/dd081d461435c292db356f976041ca01a94515a2 https://github.com/spring-projects/spring-data-commons/commit/88011e6003e95bba721bb5680b049db0f9c9eaed
Not sure Boot could benefit from it.
Comment From: quaff
public interface ExampleRepository<T extends AbstractData> extends MongoRepository<ExampleData, String> {
}
I don't understand why ExampleRepository is generic? @pavlo-reshetniak
Comment From: wilkinsona
We may get something in Framework 6.2 that helps with this. Until then, we'll have to refine our own matching.