Martin Meyer opened SPR-14083 and commented
If I have a Transaction Manager running in unit tests, it becomes impossible for Mockito to properly initialize and inject mocks. This problem seems to be related to usage of proxy classes, not specific to using a transaction manager.
Here's a basic example of a test class:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {EnableTransactionManager.class})
public class SomeTest {
@Mock
private MockBean someMockedBean;
@Autowired
@InjectMocks
private RealBean someRealBean;
@Before
public void before() {
MockitoAnnotations.initMocks(this);
}
@Test
public void someTest() {
someRealBean.doSomething();
}
}
What I see in the debugger is that on the someRealBean.doSomething()
line, someRealBean
contains a mock reference to my MockBean
instance. But when I set the next breakpoint within RealBean#doSomething()
and go there, the local field within the object is not a mock one, it's a real @Autowire
'd bean.
In real terms, the impact on me is that tests fail whenever I try to implement a TransactionManager and have it configured during tests. I end up trying to call DAO methods that should have been mocked, and it tries to issue database calls on a DataSource that isn't really ready to service them.
I think the problem is a race between Mockito and Spring to performing their setup. I think the solution is for SpringJUnit4ClassRunner
to automagically call the equivalent of MockitoAnnotations.initMocks(this)
if org.mockito.Mockito
is in the classpath. This would mean adding an optional dependency on Mockito.
There was an issue opened against Mockito for this, which was closed as WONTFIX. They suggested that it be fixed in Spring instead of bringing in a Spring dependency there.
The obvious workaround here is to not enable anything that will do AOP proxying during your unit tests.
Affects: 4.2.5
Comment From: spring-projects-issues
Michael Huffman commented
I do not believe this is a Spring issue. Mockito's PropertyAndSetterInjection
appears to have a design flaw/limitation in that it uses field declaration of a class as a means to determine candidate injection types and properties. When a proxy is used, particularly a CGLIB proxy, the fields have no correlation to the public properties (setters) available for use in injection. Therefore, the injection filtering does not find the properties/setters suitable to inject. I have reported a comment on your original bug report in Mockito.
Comment From: spring-projects-issues
Martin Meyer commented
Has anyone looked back at this problem in the past two years? This problem persists and from a user perspective it really seems like I shouldn't have to go so far out of my way to inject mocks. Mockito has been under much heavier development lately, and with all that's changed in JUnit 5 and Spring Framework 5 maybe fixing this problem might be easier than it was when I originally filed the issue..?
Comment From: spring-projects-issues
Bulk closing outdated, unresolved issues. Please, reopen if still relevant.
Comment From: elreydetodo
This is definitely still relevant. It has tripped up other developers on almost every project I've worked on involving Spring Framework. We always get into a state early on where unit tests aren't passing and we can't figure out why, and it always turns out it's because injecting mocks on something being proxied by cglib doesn't work as expected. Once we re-realize the problem, we start using ReflectionTestUtils
to set the field, but then everyone forgets why we're doing it in some tests and not others, and we run amok using ReflectionTestUtils
almost everywhere instead of @InjectMocks
.
We really could use a better system to do this for us. This shouldn't be so hard and manual. Even discovering that this is a problem isn't that easy.
Comment From: philwebb
@elreydetodo Have you seen the @MockBean
annotation from Spring Boot?
Comment From: philwebb
A similar proxy issue was raised in Spring Boot https://github.com/spring-projects/spring-boot/issues/6871 with @MockBean
Comment From: spring-projects-issues
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Comment From: elreydetodo
Actually it looks like that would resolve the problem. I'm reading a little into that issue and the Javadoc of MockBean
and MockitoTestExecutionListener
, and it sounds like this thing is what I want, except for some reason it's done in Spring Boot instead of as part of the core framework.
Can we get that annotation and the test execution listener promoted to spring-test?
Comment From: elreydetodo
Please note that I'm not using Spring Boot, so I can't simply use that annotation and be done with it. Spring Boot doesn't seem like the appropriate place for such fundamental functionality to live.
Comment From: greylurk
It's been about a year since it was suggested to move the MockBean annotation (and its affiliated functionality) from the spring-boot project to the spring-test project. What's involved in completing that move?
Comment From: sbrannen
Superseded by #29917 which introduces support for @TestBean
, @MockitoBean
, and @MockitoSpyBean
in the spring-test
module in Spring Framework 6.2.