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
- Prevent beans created with @MockBean from being post-processed Post-processing of mocked beans causes a number of problems: - The mock may be proxied for asynchronous processing which can cause ... — committed to wilkinsona/spring-boot by wilkinsona 8 years ago
- Prevent beans created with @MockBean from being post-processed Post-processing of mocked beans causes a number of problems: - The mock may be proxied for asynchronous processing which can cause ... — committed to spring-projects/spring-boot by wilkinsona 8 years ago
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:
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 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.