Spring-boot: Mockito fails to verify the second time on the proxied bean

Created on 12 Sep 2016  路  10Comments  路  Source: spring-projects/spring-boot

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.
bug

All 10 comments

@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.

Was this page helpful?
0 / 5 - 0 ratings