Hello,

The issue

scenario we have:

  • There are several @SpringBootTest tests that test various classes and scenarios.
  • There is a lot of when(..).then(..) configuration necessary, but it's the same for all the tests.
  • I created @TestConfiguration that contains @Beans. It's @Imported to each of the test classes.
  • Beans in the @TestConfiguration class do return the configured mocks - as intended.

Up to here everything works nicely - I have one place where all the when(...).then(...) config is done, and can reuse it in each test class.

The issue is when I want to @Autowire any of the mock to use Mockito.verify() on it. Since the mocks, returned by @Bean methods get wrapped in CGLIB proxies, verify() never fails, even when it should.

We can't disable proxying for the whole app, since AOP is used (and purpose of some of the mentioned tests is to check the very AOP). We use Spring's AOP, no @EnableAspectJAutoProxy.

I noticed, that if I use @MockBean, Spring somehow knows that these beans should not be wrapped in proxies, thus verify() works correctly. But with @MockBean, I can't store the mock config in one place for all the tests as I do with the @TestConfiguration.

What didn't help

= didn't prevent mocks from being proxied:

  • @Scope(proxyMode = ScopedProxyMode.NO) on those beans in @TestConfiguration
  • @TestConfiguration(proxyBeanMethods = false) on the whole test config class
  • Not even @SpringBootApplication(proxyBeanMethods = false) (I tried it even though I need AOP being functional)
  • Replacing AnnotationAwareAspectJAutoProxyCreator with my own extension that would override the shouldSkip() method to skip proxying for certain beans
  • Both my implementation and the original AnnotationAwareAspectJAutoProxyCreator ended up running, causing crashes when proxied beans were required
  • and such colution would be rather cumbersome anyway

What helped

& why I didn't like it:

  • Using AopTestUtils.getTargetObject(cglibWrappedMock) before calling verify()
  • but that's something one has to keep in mind, seems cumbersome to me
  • Using @MockBean on a field inside the @TestConfiguration class to obtain the mock and then do the configuration in a bean method and annotating that with @Primary @Bean
  • but that's rather cumbersome and not very elegant, required creating new @Bean names and using @Qualifier with those...

My proposal...

...would be to make @MockBean applicable to bean methods in @TestConfiguration classes (and perhaps everywhere?).

So it would be used in the @TestConfiguration instead of @Bean. That way Spring would know that I want to create and configure the mock myself, but otherwise it should be non-proxied the same way as when @MockBean is used on a field in @SpringBootTest.

Thanks for any feedback!

Comment From: wilkinsona

@MockBean has been deprecated in Spring Boot 3.4 in favor of the new @MockitoBean support in Spring Framework 6.2. This means that we won't be making any improvements to it. Given this, I don't think there's much that we can do here so I'll close this issue. Before doing so, I'll try to offer some suggestions although it's difficult to offer anything specific without having seen some examples of the problem that you're facing.

proxyBeanMethods on @TestConfiguration and @SpringBootApplication controls whether or not the annotated configuration classes are proxied such that one @Bean method can call another @Bean method and get the same singleton bean method. It has no effect on whether or not the beans returned by those methods are proxied.

So it would be used in the @TestConfiguration instead of @Bean. That way Spring would know that I want to create and configure the mock myself, but otherwise it should be non-proxied the same way as when @MockBean is used on a field in @SpringBootTest.

IIRC, this isn't how @MockBean works. The mocks may still be proxied and the proxied mock will be injected into the @MockBean-annotated field. The proxy is then unwrapped by SpringBootMockResolver that's registered through a mockito-extensions/org.mockito.plugins.MockResolver file. I'm surprised that this same resolver doesn't work in your case as I believe it should still be involved. You may want to explore this and see if you can determine why the resolver's not being used.

Using AopTestUtils.getTargetObject(cglibWrappedMock) before calling verify()

This is the approach that I would take, either by calling getTargetObject directly or by using a MockResolver of your own to perform the unwrapping.

Comment From: RichardBenes

Thanks for a quick reply. I wasn't at all aware of the @MockBean deprecation. And thanks as well for pointing out the SpringBootMockResolver, that seems as a good direction to look to.

Comment From: RichardBenes

FYI - it feels kind of stupid given the amount of digging we did, but this is the solution that works:

  @MockBean
  @Autowired
  TheMockedClass theMockedClass;

Just using both @MockBean and @Autowired.

What lead me to trying this was your mentioning the SpringBootMockResolver though - so thanks again.

Comment From: RichardBenes

FYI - it feels kind of stupid given the amount of digging we did, but this is the solution that works:

@MockBean @Autowired TheMockedClass theMockedClass;

Just using both @MockBean and @Autowired.

What lead me to trying this was your mentioning the SpringBootMockResolver though - so thanks again.

Correction - this actually doesn't work as intended. The CGLIB proxied mock is indeed replaced with a non-proxied one, but it's not proxied with the one from the @TestConfiguration. So this solution isn't working for this case.