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 righttestTransactionalMethod() 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 @TransactionaltestTransactionalMethod() 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!
verify, the test will become green.@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.
@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)
@snicoll @philwebb I discovered this issue by using @SpyBean in an integration test for our Spring Boot web application.
@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?
@igormukhin Also, please ensure you're using 1.4.1.BUILD-SNAPSHOT since there are know issues with 1.4.0.RELEASE.
@wilkinsona @philwebb Here is the project you requested with @SpyBean - https://github.com/igormukhin/spring-boot-issue-6871
Same results for the snapshot.
Thanks! I think we need to refine our MockitoAopProxyTargetInterceptor
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
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.
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.