spring-boot: @MockBean doesn't work with @Async annotated services

Hello everybody,

I want to test a MVC @Controller with 1.4.0.RC1 using @WebMvcTest. The controller depends on a service which as an @Async method:

@RunWith(SpringRunner.class)
@WebMvcTest(TestController.class)
public class TestControllerTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private TestService testService;

    @Test
    public void testSomeMethod() {
        // blah...
    }
}

The service:

package com.example;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class TestService {

    @Async
        public int doSomethingElse() {
            return 4711;
        }
}

Test fails with

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'testController' defined in file [/Users/msimons/Downloads/mock_async_proxy_bug/target/classes/com/example/TestController.class]: Bean instantiation via constructor failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.TestController]: Illegal arguments for constructor; nested exception is java.lang.IllegalArgumentException: argument type mismatch
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:279) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1143) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1046) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:510) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:775) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:861) ~[spring-context-4.3.1.RELEASE.jar:4.3.1.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:541) ~[spring-context-4.3.1.RELEASE.jar:4.3.1.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759) ~[spring-boot-1.4.0.RC1.jar:1.4.0.RC1]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:369) ~[spring-boot-1.4.0.RC1.jar:1.4.0.RC1]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:313) ~[spring-boot-1.4.0.RC1.jar:1.4.0.RC1]
    at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:111) ~[spring-boot-test-1.4.0.RC1.jar:1.4.0.RC1]
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98) ~[spring-test-4.3.1.RELEASE.jar:4.3.1.RELEASE]
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116) ~[spring-test-4.3.1.RELEASE.jar:4.3.1.RELEASE]
    ... 25 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.TestController]: Illegal arguments for constructor; nested exception is java.lang.IllegalArgumentException: argument type mismatch
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:156) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:122) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:271) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
    ... 42 common frames omitted
Caused by: java.lang.IllegalArgumentException: argument type mismatch
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_92]
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_92]
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_92]
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_92]
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:147) ~[spring-beans-4.3.1.RELEASE.jar:4.3.1.RELEASE]
    ... 44 common frames omitted

My first guess that this is related to #5837 or similar. Throwing @EnableAsync at the test didn’t help either, using a separate context configuration would defeat the purpose of WebMvcTest i guess

Example project is attached mock_async_proxy_bug.tar.gz Actual use case was writing a test for this controller in preparation for 1.4.

Thanks, Michael.

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 23 (21 by maintainers)

Commits related to this issue

Most upvoted comments

It looks like the @Async support has created the wrong sort of proxy. It’s trying to inject a JDK proxy which has been created because the mocked bean implements a single interface, org.mockito.cglib.proxy.Factory.

A workaround is to force the creation of CGLib proxies:

@EnableAsync(proxyTargetClass=true)

I ran into the same issue. after add "proxyTargetClass = true" it works. it’s a bug or not? thanks

I’ve opened #6434 for the FailureAnalyzer and we have #6318 for the Spring Framework 4.3.2 upgrade which fixes this issue’s specific problem.

@michael-simons Sorry. I meant false. I’ve updated my comment.

Thanks everybody, for the explanation and workaround and also for the last comment, that is exactly which I would expect as a user, @wilkinsona.

I disagree, for now at least. Users shouldn’t have to know any of that. The ideal is that it just works. I doubt we’ll get there in Boot 1.4 and Framework 4.3, but there’s still plenty of scope for improvement. I’d like to wait and see what SPR-14478 brings us.

I was hoping I could overwrite without introducing new configuration classes by adding it to the test class itself.

I wouldn’t recommend doing that. It doesn’t feel like a good idea to be running your tests with different configuration from normal runtime.

It feels like the best that we can do here is to make it more obvious that you need to configure proxyTargetClass=true. I’ve opened SPR-14478 to see what can be done at the Spring Framework level.