Trying to mock a @SessionScope
in a test by autowiring it using @SpyBean
and then mocking using doReturn(..).when(sessionScopedBean).method(any())
calls the real method with null argument instead of silently registering mock.
Removing @SessionScope
from the bean resolves this issue.
Spring boot 2.1.6.RELEASE
Comment From: mbhave
@cdalexndr Please provide a minimal sample that we can use to reproduce the issue.
Comment From: cdalexndr
https://github.com/cdalexndr/spring-boot-issue-17817
Just run gradlew test
Note that removing @SessionScope
fixes test.
Comment From: blindpirate
I did some debugging. Seems like that @SessionScope
is enhanced by Spring so every method in it becomes final
, thus Mockito can't enhance it again. I'm wondering, why does Spring make all methods final when enhancing the bean? Can we somehow only make the injected methods final, but keep all declared methods non-final?
Comment From: wilkinsona
Thanks for the sample, @cdalexndr.
When you use @SpyBean
, the spy is created via SpyPostProcessor
which is an InstantiationAwareBeanPostProcessorAdapter
and it is PriorityOrdered
with highest precedence. This is intended to ensure that the spy is created before any proxying performed by Spring Framework. It works for @Scheduled
, @Async
, etc, but it does not work in the case of a scoped proxy as the proxying is done as part of component scanning rather than bean post-processing. Switching to a @Bean
method on a @Configuration
class does not help as ConfigurationClassBeanDefinitionReader
does the same thing.
I can't see a way to fix this without some changes in Spring Framework. Flagging for team attention in case I've missed something.
Comment From: wilkinsona
We might be able to get the bean definition for the target as both it and the scoped proxy are added to the bean factory. We could perhaps then replace the target with the definition for the spy.
Comment From: wilkinsona
@cdalexndr While we're working on a fix, you can work around the problem by spying upon the scoped proxy's target which is the underlying SessionScopeBean
instance that has not be proxied and, therefore, can be spied upon:
@SpyBean(name="scopedTarget.sessionScopedBean")
Comment From: elxnis
Hi @wilkinsona ,
could your fix for this cause issues with @SpyBean
on @StepScope
d beans?
I have spring boot application that uses spring batch, so I have couple beans that are @StepScope
d and I am using @SpyBean
on them in my tests, but they have started to fail since updating to spring boot 2.1.9.RELEASE from 2.1.7.RELEASE.
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.beanName': Scope 'step' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No context holder available for step scope
Comment From: wilkinsona
That looks possible. It's certainly in the same area. Please open a new issue with a small sample that reproduces the issue and we can take a look.
Comment From: wilkinsona
Please note that, unfortunately, we've had to revert the fix for this issue. The problem can be avoided by adding a dependency on org.mockito:mockito-inline
to your application's test dependencies.
Comment From: cdalexndr
@wilkinsona if that dependency fixes this issue, shouldn't be added to spring-boot-starter-test to be general available?
Comment From: wilkinsona
No, I don't think so. It fundamentally changes the way in which Mockito behaves and the Mockito team have chosen to make it opt-in for that reason. If Mockito makes it the default in the future then we would probably do the same in Spring Boot. Until then, I think it should remain opt-in so that users have a natural point at which to consider the implications.
Comment From: cdalexndr
@wilkinsona then if the fix was reverted and the workaround cannot be made general available due to side effects, shouldn't this issue be reopened, as a reminder, until a new fix is made?
Comment From: wilkinsona
We've already made the only "fix" that we can, and that is to add a note to the documentation about mockito-inline
.