Version: Spring Boot 1.4.0-RELEASE
It looks like there is an issue with verifing multiple times with Mockito on a proxied bean.
Test-case:
public class SpringBootMockitoTest {
private AnnotationConfigApplicationContext context;
private SomeServiceWithTransact someServiceWithTransact;
private SomeServiceNoTransact someServiceNoTransact;
@Configuration
@EnableTransactionManagement
public static class Config {
@Bean
public SomeServiceWithTransact someServiceWithTransact() {
return new SomeServiceWithTransact();
}
@Bean
public SomeServiceNoTransact someServiceNoTransact() {
return new SomeServiceNoTransact();
}
@Bean
public PlatformTransactionManager tm() {
return new DataSourceTransactionManager(dataSource());
}
@Bean
public DataSource dataSource() {
return DataSourceBuilder.create()
.driverClassName("org.h2.Driver")
.url("jdbc:h2:mem:tst;DB_CLOSE_DELAY=-1")
.username("sa")
.password("")
.build();
}
}
private static class SomeServiceNoTransact {
public void normalMethod(int param1) {
// do nothing
}
}
private static class SomeServiceWithTransact {
@Transactional
public void transactionalMethod(int param1) {
// do nothing
}
}
@Before
public void setUp() throws Exception {
context = new AnnotationConfigApplicationContext(Config.class);
context.start();
someServiceWithTransact = context.getBean(SomeServiceWithTransact.class);
someServiceNoTransact = context.getBean(SomeServiceNoTransact.class);
}
@After
public void tearDown() throws Exception {
context.stop();
}
@Test
public void testNormalMethod() throws Exception {
SomeServiceNoTransact serviceSpy = spy(someServiceNoTransact);
// when
serviceSpy.normalMethod(1);
// then
Mockito.verify(serviceSpy, Mockito.times(1)).normalMethod(1);
Mockito.verify(serviceSpy, Mockito.times(1)).normalMethod(anyInt());
}
@Test
public void testTransactionalMethod() throws Exception {
SomeServiceWithTransact serviceSpy = spy(someServiceWithTransact);
// when
serviceSpy.transactionalMethod(1);
// then
Mockito.verify(serviceSpy, Mockito.times(1)).transactionalMethod(1);
Mockito.verify(serviceSpy, Mockito.times(1)).transactionalMethod(anyInt());
}
}
Now:
- testNormalMethod()
will be green and all right
- testTransactionalMethod()
will be red and it is not all right. The only difference to the first test in the code is that the method unter test is annotated with @Transactional
testTransactionalMethod()
fails with:
org.mockito.exceptions.misusing.UnfinishedVerificationException:
Missing method call for verify(mock) here:
-> at de.audi.pbt.problem.service.MockitoIT.testTransactionalMethod(MockitoIT.java:123)
But!
- If you remove @Transactional form the bean unter test, the test will become green.
- If you remove the first call to verify
, the test will become green.
Comment From: snicoll
@igormukhin there's nothing specific to Spring Boot in your project. I have the feeling that it could be a mockito usage problem. Please create an issue in the Spring Framework issue tracker.
Also, rather than pasting the full code in the description, please create a project that one can run, it's much more convenient.
Comment From: philwebb
@igormukhin We faced a very similar issue with the new @MockBean
and @SpyBean
annotations (see #5837). Have you tried using them instead of calling the spy
method directly (you'll need to try with Spring Boot 1.4.1.BUILD-SNAPSHOT
)
Comment From: igormukhin
@snicoll @philwebb I discovered this issue by using @SpyBean
in an integration test for our Spring Boot web application.
Comment From: wilkinsona
@igormukhin In that case the code above isn't much help as it doesn't use @SpyBean
. Can you please provide a complete, minimal sample that illustrates the problem you're having and uses @SpyBean
?
Comment From: philwebb
@igormukhin Also, please ensure you're using 1.4.1.BUILD-SNAPSHOT
since there are know issues with 1.4.0.RELEASE
.
Comment From: igormukhin
@wilkinsona @philwebb Here is the project you requested with @SpyBean - https://github.com/igormukhin/spring-boot-issue-6871 Same results for the snapshot.
Comment From: philwebb
Thanks! I think we need to refine our MockitoAopProxyTargetInterceptor
Comment From: sbrannen
FYI: the solution to the original code example is simply to invoke AopTestUtils.getUltimateTargetObject()
, passing in the someServiceWithTransact
that was retrieved from the ApplicationContext
.
For example, the following works fine:
@Test
public void testTransactionalMethod() throws Exception {
SomeServiceWithTransact ultimateTargetObject = AopTestUtils.<SomeServiceWithTransact> getUltimateTargetObject(
someServiceWithTransact);
SomeServiceWithTransact serviceSpy = spy(ultimateTargetObject);
// when
serviceSpy.transactionalMethod(1);
// then
verify(serviceSpy, times(1)).transactionalMethod(1);
verify(serviceSpy, times(1)).transactionalMethod(anyInt());
}
Regards,
Sam
Comment From: kriegaex
Thanks @sbrannen for the workaround, I just used it in order to answer StackOverflow question #62698827. (BTW, I never used Spring, I am just interested in AOP topics and stumbled upon the question.) There you can also find a link to yet another GitHub sample project.
I am mentioning this in order to remind any possibly involved maintainers that this is still an open issue which ought to be addressed eventually.
Comment From: wilkinsona
Thanks, @kriegaex. While you can use the same technique to avoid the problem described on Stack Overflow, it is a different problem to the one fixed in this issue. I'm not sure if we'll be able to automatically avoid the advice being executed when setting up expectations on the spy but we can certainly take a look. I've opened https://github.com/spring-projects/spring-boot/issues/22281.