With thanks to @kriegaex, see this question on Stack Overflow and the sample to which it links.

Comment From: wilkinsona

To fix this, we need to be able to automatically unwrap the proxy as is being done here:

doNothing()
    .when(
        // fetch spy bean by unwrapping the AOP proxy, if any
        AopTestUtils.<KafkaService>getTargetObject(kafkaService)
    )
    .send(ArgumentMatchers.any());

Unfortunately, I don't think there's anything in Mockito's current public API that will notify us of the when call and allow us to get the target object and have it be the value that's returned from when(). I think we need an equivalent of VerificationStarterListener (which was added to Mockito as a result of https://github.com/spring-projects/spring-boot/issues/10352) for when the expectations are being set up.

@mockitoguy If you have a moment, I'd love your expertise here please. I'm wondering if there's something that I've missed in Mockito that would provide the necessary interception point.

Comment From: mockitoguy

Hey!

One thing to try is to see if "InvocationListener" works for your use case. I don't remember off top of my head if it reports stubbings or only regular invocations (and whether it works with when and doReturn stubbings)

Adding new listener is conceivable. My understanding is that the "sample" link in the description reproduces this very issue? I'm a little busy next couple of days but I'm happy help.

Comment From: wilkinsona

Thanks, @mockitoguy.

Thinking about this some more, I'm not sure that we can fix this at the Mockito level. If the method being stubbed returned something other than void, you'd write something like given(mock.method()).willReturn(42) and at that point the call to mock.method() will happen before there's any opportunity to unwrap the proxy. Requiring people to flip that around and write doReturn(42).when(mock).method(), doesn't feel like a good solution.

I'm starting to believe that we may not always be able to do the right thing when proxying is involved. We may just have to make working with that proxy and getting its underlying target easier and more obvious.

Comment From: wilkinsona

We've opened spring-projects/spring-framework#33743 to provide a nicer API for dealing with spies that are also Spring proxies. In the meantime, manually removing the Spring proxies by calling getTargetObject() is the way to go. We can use this issue to update the documentation to recommend its usage when spying on a Spring proxy.

Comment From: sbrannen

Commit b53f54f2cfc59bc044ff43f997bf8bfeb050089b incorrectly states:

Use AopTestUtils.getTargetProxy(yourProxiedSpy) to do so.

Whereas, that should read:

Use AopTestUtils.getTargetObject(yourProxiedSpy) to do so.

Comment From: wilkinsona

Well spotted, @sbrannen. Thank you. I've corrected it in https://github.com/spring-projects/spring-boot/commit/63f7c75b61d521f4e84a939f74c65344c4d80cd7.

Comment From: mockitoguy

Sounds good, thanks!