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.