spring-framework
spring-framework copied to clipboard
Support strict stubbing for `@MockitoBean` and `@MockitoSpyBean`
Overview
As explained in #33692, due to limitations in Mockito it is not currently possible to support strict stubbing for mocks and spies created via @MockitoBean and @MockitoSpyBean.
This issue therefore serves as a placeholder to collaborate with the Mockito team to investigate a way to provide such support.
Known Issues
- It is impossible for two frameworks to use the
MockitoSessionsimultaneously. Only oneMockitoSessioncan exist at any given time for the current thread. - The
MockitoTestExecutionListenerthat existed during the 6.2 milestone phase attempted to emulate the behavior of Mockito's ownMockitoExtension; however, a SpringTestExecutionListenercannot achieve the same level of integration into JUnit Jupiter's extension model.- A Spring
TestExecutionListenercannot access the enclosing test instances for@Nestedtest classes. - A Spring
TestExecutionListenercannot store state in a parentTestContext(simply because there is no parent). - Consequently, the
MockitoTestExecutionListenerprovided less benefit than using theMockitoExtensionwhen it comes to JUnit Jupiter support.
- A Spring
Results of exploratory research
MockitoSession is effectively a "Unit of Work" that is bound to the current thread via a ThreadLocal. Mocks created between the "start" and "finish" of the session are tracked for the current thread only. Mocks created outside that session -- for example, before the session or in another thread -- are not tracked by the session. In addition, there is no API that allows one to attach an existing mock to a MockitoSession, and there is no API to query whether a MockitoSession already exists for the current thread.
The following provides an overview of how MockitoSession technically works.
startMocking()invokes:MockitoAnnotations.openMocks()startMocking()registers:org.mockito.internal.junit.UniversalTestListenerfinishMocking()unregisters:org.mockito.internal.junit.UniversalTestListenerfinishMocking()invokes:Mockito.validateMockitoUsage()
Hypothetically (and this is not tested in any way, shape, or form), if we wanted to mimic MockitoSession support for mocks created via @MockitoBean and @MockitoSpyBean, we could track the MockCreationSettings in MockitoBeanOverrideMetadata.createMock() and then register a (subclass of) UniversalTestListener via Mockito.framework().addListener() and invoke CustomSubclassOfUniversalTestListener.onMockCreated(Object, MockCreationSettings) with the mock created by MockitoBeanOverrideMetadata and the saved MockCreationSettings in MockitoTestExecutionListener.beforeTestMethod(), and we could then invoke Mockito.framework().removeListener() and Mockito.validateMockitoUsage() in MockitoTestExecutionListener.afterTestMethod().
However, UniversalTestListener is an internal implementation detail of Mockito and resides in the org.mockito.internal.junit package, and we should ideally not rely on Mockito internals that may change unexpectedly.
Instead, we should approach the Mockito team to discuss alternatives to the current MockitoSession API and semantics that would allow us to provide similar support to mocks that we choose and within a scope that we define.
Related Issues
- #33318
- #33690
- #33692
Thanks for opening this issue @sbrannen as a follow-up to #33318.
Instead, we should approach the Mockito team to discuss alternatives to the current MockitoSession API and semantics that would allow us to provide similar support to mocks that we choose and within a scope that we define.
I'm assuming this last paragraph of your description is the reason for this issue being in status: blocked? If so, is there anything in Mockito issue tracker where we could follow the progress?
@sbrannen I see this is still assigned to the 7.0.x milestone but since RC1 has been released last week I guess that ship has sailed, right? Could you provide some info about the status of this issue?