Version: Spring Boot 1.4.1
Subject: @SpyBean on Data Jpa Repository bean isn't working
Exception thrown:
UnsatisfiedDependencyException: Error creating bean with name 'cityService' defined in file [...\target\classes\com\example\CityService.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cityRepository': Initialization of bean failed; nested exception is java.lang.IllegalArgumentException: Object of class [org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean] must be an instance of interface com.example.CityRepository
Demonstration project: https://github.com/igormukhin/spring-boot-issue-6871 USE BRANCH: spybean-on-jparepository
Comment From: igormukhin
Nobody cares 😢 @philwebb @wilkinsona @snicoll
Comment From: philwebb
We care, we're just snowed under at the moment!
The root of the problem is that SpyPostProcessor is used postProcessBeforeInitialization which is passed a FactoryBean and not the actual instance that needs mocking. I tried changing the process to use postProcessAfterInitialization:
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof FactoryBean) {
return bean;
}
return createSpyIfNecessary(bean, beanName);
}
This gets us further but doesn't fix the issue because Mockto isn't able to spy on the Proxy:
Cannot mock/spy class com.example.$Proxy81
Mockito cannot mock/spy following:
- final classes
- anonymous classes
- primitive types
2016-10-10 19:11:11.065 INFO 63856 --- [ main] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2016-10-10 19:11:11.066 INFO 63856 --- [ main] org.hibernate.tool.hbm2ddl.SchemaExport : HHH000227: Running hbm2ddl schema export
2016-10-10 19:11:11.068 INFO 63856 --- [ main] org.hibernate.tool.hbm2ddl.SchemaExport : HHH000230: Schema export complete
2016-10-10 19:11:11.076 INFO 63856 --- [ main] utoConfigurationReportLoggingInitializer :
Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled.
2016-10-10 19:11:11.084 ERROR 63856 --- [ main] o.s.boot.SpringApplication : Application startup failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'cityService' defined in file [/Users/pwebb/projects/spring-boot/samples/spring-boot-issue-6871/target/classes/com/example/CityService.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cityRepository': Post-processing of FactoryBean's singleton object failed; nested exception is org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class com.example.$Proxy81
Mockito cannot mock/spy following:
- final classes
- anonymous classes
- primitive types
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:189) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1148) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1051) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:510) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:751) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:861) ~[spring-context-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:541) ~[spring-context-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:761) ~[classes/:na]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:371) ~[classes/:na]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[classes/:na]
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:111) [classes/:na]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:189) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:131) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192) [.cp/:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cityRepository': Post-processing of FactoryBean's singleton object failed; nested exception is org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class com.example.$Proxy81
Mockito cannot mock/spy following:
- final classes
- anonymous classes
- primitive types
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:116) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1600) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:317) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:207) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1128) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1056) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:835) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
... 43 common frames omitted
Caused by: org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class com.example.$Proxy81
Mockito cannot mock/spy following:
- final classes
- anonymous classes
- primitive types
at org.springframework.boot.test.mock.mockito.SpyDefinition.createSpy(SpyDefinition.java:99) ~[classes/:na]
at org.springframework.boot.test.mock.mockito.MockitoPostProcessor.createSpyIfNecessary(MockitoPostProcessor.java:332) ~[classes/:na]
at org.springframework.boot.test.mock.mockito.MockitoPostProcessor$SpyPostProcessor.createSpyIfNecessary(MockitoPostProcessor.java:490) ~[classes/:na]
at org.springframework.boot.test.mock.mockito.MockitoPostProcessor$SpyPostProcessor.postProcessAfterInitialization(MockitoPostProcessor.java:486) ~[classes/:na]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:422) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.postProcessObjectFromFactoryBean(AbstractAutowireCapableBeanFactory.java:1728) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:113) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
... 51 common frames omitted
2016-10-10 19:11:11.086 ERROR 63856 --- [ main] o.s.test.context.TestContextManager : Caught exception while allowing TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener@7dc222ae] to prepare test instance [com.example.SpringBootIssueApplicationTests@794eeaf8]
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124) ~[spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83) ~[spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:189) ~[spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:131) ~[spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230) ~[spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) [junit-4.12.jar:4.12]
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12]
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) [spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) [.cp/:na]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192) [.cp/:na]
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'cityService' defined in file [/Users/pwebb/projects/spring-boot/samples/spring-boot-issue-6871/target/classes/com/example/CityService.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cityRepository': Post-processing of FactoryBean's singleton object failed; nested exception is org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class com.example.$Proxy81
Mockito cannot mock/spy following:
- final classes
- anonymous classes
- primitive types
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:189) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1148) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1051) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:510) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:751) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:861) ~[spring-context-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:541) ~[spring-context-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:761) ~[classes/:na]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:371) ~[classes/:na]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[classes/:na]
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:111) ~[classes/:na]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98) ~[spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116) ~[spring-test-4.3.3.RELEASE.jar:4.3.3.RELEASE]
... 25 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cityRepository': Post-processing of FactoryBean's singleton object failed; nested exception is org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class com.example.$Proxy81
Mockito cannot mock/spy following:
- final classes
- anonymous classes
- primitive types
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:116) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1600) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:317) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:207) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1128) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1056) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:835) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
... 43 common frames omitted
Caused by: org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class com.example.$Proxy81
Mockito cannot mock/spy following:
- final classes
- anonymous classes
- primitive types
at org.springframework.boot.test.mock.mockito.SpyDefinition.createSpy(SpyDefinition.java:99) ~[classes/:na]
at org.springframework.boot.test.mock.mockito.MockitoPostProcessor.createSpyIfNecessary(MockitoPostProcessor.java:332) ~[classes/:na]
at org.springframework.boot.test.mock.mockito.MockitoPostProcessor$SpyPostProcessor.createSpyIfNecessary(MockitoPostProcessor.java:490) ~[classes/:na]
at org.springframework.boot.test.mock.mockito.MockitoPostProcessor$SpyPostProcessor.postProcessAfterInitialization(MockitoPostProcessor.java:486) ~[classes/:na]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:422) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.postProcessObjectFromFactoryBean(AbstractAutowireCapableBeanFactory.java:1728) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:113) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
... 51 common frames omitted
Comment From: philwebb
@igormukhin is there any particular reason why you need to use @SpyBean? Obviously we'd like to fix this, but I'm not sure how easy it will be. You might be better to either try @MockBean or switching to a test that actually uses the database result, rather than a spy.
Comment From: igormukhin
@philwebb I stunble upon it as I worked on an integration test where I wanted to check if a specific save operation is called at the end. But still I needed that all other (find... methods) to work as usual.
As a workaround I'm checking if the saved entry is in the database.
Comment From: ianaz
+1. Same reason here
Comment From: micke239
+1. Same issue for the same reason (wanting to verify interactions). Using spring-data-mongo, though.
Comment From: hashpyrit
I encountered this problem as well. I got around it by creating my mock as follows:
MyClass spyOfSpringProxy = Mockito.mock(MyClass.class, AdditionalAnswers.delegatesTo(springCreatedProxy));
AdditionalAnswers.deletegateTo() works because the object you are delegating to does NOT have to be the same type as the type of the mock. I was able to use it in the same manner as a regular Mockito spy and was able to verify interactions.
Comment From: odrotbohm
When wanting to test interactions isn't a plain unit test of the client with a mock of the repository sufficient? It wouldn't even need to be an integration then would it?
It feels like you're mixing up two aspects of testing here: integration tests that check the behavior end to end and the desire to verify on internals of the tested component. I'd argue that's sort of violating the SRP principle in tests (if there is such thing in tests). Either test the thing as whole, then it's inputs against output. Or a dedicated component whose interaction with collaborators you inspect in detail.
Comment From: hashpyrit
My specific use-case was verifying that database transactions were being rolled back as expected if a call to the repository (made in a service method) failed. Transaction was being applied as the service level. Hence I wanted to make the repository throw an exception. I wanted to use the functionality real repository most of the time except for the failure case. A spy was appropriate here. The component under test was the service but due to the nature of what it was doing, simply mocking the repository would not have been as complete a test as using a real repository.
Comment From: odrotbohm
So were you testing that an exception within a transaction rolls back the transaction? Then you're basically testing Spring Frameworks transaction implementation, don't you?
Comment From: hashpyrit
I'm making sure I am using Spring transactions properly. I chose to use the @Transactional annotation to do this. It is conceivable that someone could remove that annotation not knowing that removing the annotation would prevent the transaction from being applied, and thus my service method would not function as it should. This why I wanted to test this. I'm not testing Spring transactions per se, but my usage of Spring transactions.
Comment From: odrotbohm
Gotcha, good point! 👍 I guess switching to a mock would then break the other tests that really want to persist data, right?
Comment From: hashpyrit
Honestly like 99% of the time I use mocks for my dependencies (e.g. repositories) when making tests for my services. And most of the time my service methods aren't making multiple calls to a repository methods so there really is not much point in making the service method transactional. It was just this case of using the @Transactional annotation that I wanted to make sure the transaction was rolled back in case of an error thrown by the repository. By spying on the repository I was able to make calls to the repo, force a failure and then check after that the changes were NOT committed (because of the rollback). I previously tried using a mock repository and hooking into the Spring Transaction mechanism to listen for "rollback events" but I could not get it to work.
Comment From: hashpyrit
At the end of the day, it's not too often people have to use spies but there are cases when it's the option that gets the job done.
Comment From: robertotru
Mine is just a guess, but maybe using Mockito's @Spy on the bean created via @Resource and then injected by using @InjectMock should act as as a replacement of the @SpyBean annotation.
Am I wrong?
Comment From: hashpyrit
I assume you mean the @InjectMocks annotation provided by Mockito? In my case I am using the SpringRunner Junit runner. I don't think @InjectMocks would work with that because I thought you needed to use the MockitoJunitRunner for annotations like @Mock and @InjectMocks to work.
Comment From: robertotru
Yeah, right! I made a test and I could only make it working by annotating the autowired beans with @Spy and then setting the spied beans in the tested component using ReflectionTestUtils.setField. I wonder if there is out of there a runner delegator as we have one for PowerMock, so that the main runner will be MockitoJUnitRunner and the delegated will be SpringRunner.
Comment From: kuhnroyal
I followed @hashpyrit's advice, using a special configuration for tests. It works well and is completely decoupled from the test cases.
@Configuration
public class MockRepositoryConfiguration {
@Primary
@Bean(name = "fooRepositoryMock")
FooRepository fooRepository(final FooRepository real) {
// workaround for https://github.com/spring-projects/spring-boot/issues/7033
return Mockito.mock(FooRepository.class, AdditionalAnswers.delegatesTo(real));
}
}
Comment From: renannprado
I have the same issue, but the AdditionalAnswers.delegatesTo(real) trick is not working 100% for me.
When I try to verify(myRepository, times(1)).save(myCaptor.capture()), I get the exception just like I was calling the real method passing null:
java.lang.IllegalArgumentException: Entity must not be null! at org.springframework.util.Assert.notNull(Assert.java:134) at org.springframework.data.mongodb.repository.support.SimpleMongoRepository.save(SimpleMongoRepository.java:77)
help, please!
Comment From: hashpyrit
Can you show the code that is setting up your mocks and test?
Comment From: renannprado
@Configuration
public class TestConfiguration {
@Primary
@Bean(name = "delegatedMockRepository")
public MyRepository myMockRepository(final MyRepository myRealRepository) {
return Mockito.mock(MyRepository.class, AdditionalAnswers.delegatesTo(myRealRepository));
}
}
Comment From: hashpyrit
Here is what I think is happening: When you use AdditionalAnswers.delegatesTo it will pass the method invocation to the real object unless you specify mocked behavior for the method (e.g. using when(...)). In terms of what happens when you do a verify(...) i am not sure. So in your case it is passing the method invocation of save(...) to the real object. I assume you are mocking the behavior of the save method in your test?
Comment From: mvamax
@hashpyrit i have the same test to do with spring boot -> testing transactional behavior of a service which rely on repositories, do you have example of how u manage to do it?
Comment From: hashpyrit
In my case, the @Transactional annotation is on a service that calls a Spring data repository. In my test for the service, I create a mock repository bean that delegates to the real repository bean (as shown before). This way I am able to cause the mock repository bean to throw and exception. The exception should cause a transaction rollback meaning that any repository calls done in that service method should be rolled back. You can verify that the changes were rolled back by querying the persisted data and seeing if it is in the state that you expect.
On Thu, Jun 8, 2017 at 3:27 PM, mvamax notifications@github.com wrote:
@hashpyrit https://github.com/hashpyrit i have the same test to do with spring boot -> testing transactional behavior of a service which rely on repositories, do you have example of how u manage to do it?
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/spring-projects/spring-boot/issues/7033#issuecomment-307203095, or mute the thread https://github.com/notifications/unsubscribe-auth/AMHgeVKZBrr-ts3zamxN3ppz6mJbUb_Pks5sCEsHgaJpZM4KHrYJ .
Comment From: mvamax
@hashpyrit thank you, I tried this
@Configuration
public class TestConfiguration {
@Primary
@Bean(name = "demandeInscriptionRepositoryMock")
public DemandeInscriptionRepository demandeInscriptionRepositoryMock(final DemandeInscriptionRepository real) {
return Mockito.mock(DemandeInscriptionRepository.class, AdditionalAnswers.delegatesTo(real));
}
}
and then in my test
@RunWith(SpringRunner.class)
@TestPropertySource(locations = "classpath:test.properties")
@SpringBootTest
public class DemandeInscriptionTest {
@Autowired
DemandeInscriptionService demandeInscriptionService;
@Autowired
DemandeInscriptionRepository demandeInscriptionRepositoryMock;
@Test
public void test() {
given(demandeInscriptionRepositoryMock.save(any(DemandeInscription.class))).willThrow(
new RuntimeException("test"));
demandeInscriptionService.inscrire(2L);
}
}
It seems it fail on the ligne given(....) with this exception.
This is not working getting a org.springframework.dao.InvalidDataAccessApiUsageException: Target object must not be null; nested exception is java.lang.IllegalArgumentException: Target object must not be null.
Did i miss something in the configuration you described?
Comment From: hashpyrit
Where are you telling your test to use the TestConfiguration class?
On Fri, Jun 9, 2017 at 1:17 AM, mvamax notifications@github.com wrote:
@hashpyrit https://github.com/hashpyrit thank you, I tried this
@Configuration public class TestConfiguration {
@Primary @Bean(name = "demandeInscriptionRepositoryMock") public DemandeInscriptionRepository demandeInscriptionRepositoryMock(final DemandeInscriptionRepository real) {return Mockito.mock(DemandeInscriptionRepository.class, AdditionalAnswers.delegatesTo(real)); } }
and then in my test
@RunWith(SpringRunner.class) @TestPropertySource(locations = "classpath:test.properties") @SpringBootTest public class DemandeInscriptionTest {
@Autowired DemandeInscriptionService demandeInscriptionService; @Autowired DemandeInscriptionRepository demandeInscriptionRepositoryMock; @Test public void test() {given(demandeInscriptionRepositoryMock.save(any(DemandeInscription.class))).willThrow( new RuntimeException("test")); demandeInscriptionService.inscrireElecteurIdentifie(2L); }
}
It seems it fail on the ligne given(....) with this exception.
This is not working getting a org.springframework.dao. InvalidDataAccessApiUsageException: Target object must not be null; nested exception is java.lang.IllegalArgumentException: Target object must not be null.
Did i miss something in the configuration you described?
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/spring-projects/spring-boot/issues/7033#issuecomment-307297484, or mute the thread https://github.com/notifications/unsubscribe-auth/AMHgeW9d26KL1z-UW3tLSeN6C3C2B3Ueks5sCNVOgaJpZM4KHrYJ .
Comment From: mvamax
@SpringBootTest
make the mechanism, it checks for all @Configuration classes. i verify that spring goes throw the method with a System.out.println.
Comment From: hashpyrit
Have you verified that the repository being injected into your test is the mockito mock and not the real spring repository?
Sent from my Galaxy Tab® A -------- Original message --------From: mvamax notifications@github.com Date: 2017-06-10 1:54 AM (GMT-05:00) To: spring-projects/spring-boot spring-boot@noreply.github.com Cc: hashpyrit allan.beharry@gmail.com, Mention mention@noreply.github.com Subject: Re: [spring-projects/spring-boot] @SpyBean on Data Jpa Repository bean isn't working (#7033) @SpringBootTest
make the mechanism, it checks for all @configuration classes. i verify that spring goes throw the method with a System.out.println.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.
{"api_version":"1.0","publisher":{"api_key":"05dde50f1d1a384dd78767c55493e4bb","name":"GitHub"},"entity":{"external_key":"github/spring-projects/spring-boot","title":"spring-projects/spring-boot","subtitle":"GitHub repository","main_image_url":"https://cloud.githubusercontent.com/assets/143418/17495839/a5054eac-5d88-11e6-95fc-7290892c7bb5.png","avatar_image_url":"https://cloud.githubusercontent.com/assets/143418/15842166/7c72db34-2c0b-11e6-9aed-b52498112777.png","action":{"name":"Open in GitHub","url":"https://github.com/spring-projects/spring-boot"}},"updates":{"snippets":[{"icon":"PERSON","message":"@mvamax in #7033: \r\n@SpringBootTest\r\n\r\nmake the mechanism, it checks for all @Configuration classes. i verify that spring goes throw the method with a System.out.println."}],"action":{"name":"View Issue","url":"https://github.com/spring-projects/spring-boot/issues/7033#issuecomment-307544969"}}}
Comment From: mvamax
@hashpyrit @philwebb Finally the delegation seems to work, the problem is on this line
given(demandeInscriptionRepositoryMock.save(any(DemandeInscription.class))).willThrow(
new RuntimeException("test"));
I try with this and the test run :
org.mockito.BDDMockito.given(demandeInscriptionRepository.findOneEager(3L)).willReturn(null);
So, in summary, delegation as suggested works but i don't know how to mock the save method of a repository i get a org.springframework.dao.InvalidDataAccessApiUsageException: Target object must not be null; nested exception is java.lang.IllegalArgumentException: Target object must not be null.
Comment From: dpinol
Hi, I couldn't make it work with the AdditionalAnswers.delegatesTo trick
- If I declare it through a bean like demandeInscriptionRepositoryMock above, I get "org.mockito.exceptions.misusing.NotAMockException: Argument passed to verify() is of type $Proxy127 and is not a mock!"
- If I declare the spy calling manually AdditionalAnswers.delegatesTo, then I get "Wanted but not invoked" on my verify call despite the method is actually called.
thanks
Comment From: hashpyrit
The source code of your test would help with debugging. Are you certain the mocked bean is being called?
Comment From: lbernardomaia
@dpinol
-
One alternative to change the behavior of a Spring Repository method in a test environment would be use Custom Repository instead of using
@SpyBean. Basically would be necessary create a custom repository for the test package and override the method that you want. -
Another option would be use the example that was suggested above, with a slight change. Example below:
@Configuration public class TestConfiguration {
@Primary
@Bean(name = "demandeInscriptionRepositoryMock")
public DemandeInscriptionRepository demandeInscriptionRepositoryMock(final DemandeInscriptionRepository real) {
MockSettings mockSetting = new MockSettingsImpl();
Answer answer = invocation -> {
if (invocation.getMethod().getName().equals("YOUR_METHOD")){
return "YOUR_RETURN";
}else{
return AdditionalAnswers.delegatesTo(real).answer(invocation);
}
};
mockSetting.defaultAnswer(answer);
return Mockito.mock(DemandeInscriptionRepository.class, mockSetting);
}
Comment From: odrotbohm
I gave this a quick spin as the topic came up in #13298 and it looks like the mock approach Mockito.mock(MyRepository.class, AdditionalAnswers.delegatesTo(bean)) from within a BeanPostProcessor.postProcessAfterInitialization(…) actually works for me. I can call real methods, mock invocations and properly verify invocations (both stubbed and delegating ones).
Given that the approach is not really producing a Mockito spy, maybe it can be integrated like asked for in #13298, i.e. as a special mode of mocking?
Comment From: hashpyrit
Ideally I would not have to use Mockito.mock(MyRepository.class, AdditionalAnswers.delegatesTo(bean)). @SpyBean is much more intuitive and the end result would be the same as in I would get a bean that would function normally but I could selectively stub out functionality as needed. E.g. throw some sort of exception from a method that would be hard to replicate without spying/mocking.
Comment From: odrotbohm
I was not saying that users are supposed to write that code themselves. I was suggesting to sort of hide that behind either @SpyBean or @MockBean and made the point, that hiding it behind the former is probably not a good idea as there's no spy generated.
Btw. here's the code I used on the simple Spring Data JPA example to be able to stub out methods on a repository: https://gist.github.com/odrotbohm/979d42f161123af7d3fca2d7bcc4a335
Comment From: odrotbohm
@marcingrzejszczak and me played a bit with the code and it looks like the setup I used will still issue calls to the real methods during stubbing (I didn't investigate why exactly).
However, switching to Answers.RETURN_DEFAULTS during mock creation basically created a completely independent mock that ignores the target repository. For Marcin's use-case that's exactly what we want but we're back to the plain mocking that's probably better hidden behind @MockBean. We still operate in a different way as we only replace the bean instance that has been created and not the BeanDefinition.
@wilkinsona / @philwebb – let me know if you'd rather revive #13298 with a slightly different rationale (control over whether to replace the bean instance or the bean definition) as what Marcin and me are trying to achieve seems to be significantly different from the spy story here.
Comment From: hashpyrit
@olivergierke I understand what you're saying. I guess I was hoping that we could somehow make @SpyBean actually work for Spring Data repository beans and create genuine Mockito spies for them as opposed to spending time making Mockito.mock(MyRepository.class, AdditionalAnswers.delegatesTo(bean)) into something more than a temporary work-around.
Comment From: odrotbohm
If you look at what Mockito.spy(…) does, it's essentially a Mockito.mock(…) but with a default answer of calling the real method. So it's sort of the same I was achieving with my BeanPostProcessor. It looks like you can avoid the real methods being called by using the doReturn(…).when(spy).methodOnSpy() style of stubbing as documented in Mockito.spy(…).
Let's see what the others say how to proceed.
Comment From: mihaita-tinta
Hi there. Another use case could be to use @SpyBean on a repository to test that a service with @Cacheable annotation will call the repository once and the second time the value is returned from the cache.
Comment From: varkychen
A combination of @hashpyrit and @odrotbohm ideas worked for me. I created a mock in a special configuration file which delegates to the actual instance. I then set the expectation to throw exception using doThrow(...).when(spy).method(...).
It was failing for with the IllegalArgumentException when using the when(spy.method(...)).thenThrow(...)
Comment From: vab2048
Any update on progress made?
Ideally I would just like to add @SpyBean in front of my repository in the test and just have it work (rather than dealing with workarounds).
Comment From: leandrodelsole
Edit: I think what I described below is not a problem, since if I wanted to use @Async in these beans wouldn't be possible as well. However, I'll keep the comment for those who are having the same issue as me, so they can have an idea how to solve their problem.
I'm not sure if it's a related problem. I thought so because both of them are related with the bean lifecycle and its wrapping on tests.
I have a Spring Boot application, with all services dependencies being through field @Autowiring.
I'm going to put the real services names in parenthesis so I can remember afterwards. But I decided to rename them because their names may confuse you.
The design is: ServiceA (ApplicationListener), that depends on ServiceB (TransactionService). ServiceB depends on a bunch of other Services, but not ServiceA. One of these Services that ServiceB depends on, let's say ServiceC (ApplicationConfigurationService), depends on ServiceA. The Spring Context of the application starts up correctly, when executing it as web server.
However, in tests, running through:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = EmmApplication.class)
@TestPropertySource("classpath:application-it.properties")
@ActiveProfiles("it")
I tried to @SpyBean ServiceB, but it throws this Exception:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'serviceB': Bean with name 'serviceB' has been injected into other beans [serviceA] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
I tried the workaround suggested here of the @Configuration to wrap ServiceA or ServiceB into a mock, but both of them throwed Exceptions of this nature.
I got to work by keeping the @SpyBean in ServiceB and @MockBean in ServiceA, then in @Before:
doAnswer((invocation) -> {
serviceB.newApplicationInCatalog(invocation.getArgument(0));
return invocation.getArgument(0);
}).when(serviceA).sendNewAppInCatalog(any());
In other words, under no circumstances I managed to work with the real bean of ServiceA.
Comment From: hashpyrit
I'm honestly surprised you application starts up at all because of the circular dependency, i.e. ServiceA -> ServiceB -> ServiceC -> ServiceA.
On Fri, Jun 28, 2019 at 9:59 AM Leandro Del Sole notifications@github.com wrote:
I'm not sure if it's a related problem. I thought so because both of them are related with the bean lifecycle and its wrapping on tests.
I have a Spring Boot application, with all services dependencies being through field @Autowiring. I'm going to put the real services names in parenthesis so I can remember afterwards. But I decided to rename them because their names may confuse you.
The design is: ServiceA (ApplicationListener), that depends on ServiceB (TransactionService). ServiceB depends on a bunch of other Services, but not ServiceA. One of these Services that ServiceB depends on, let's say ServiceC (ApplicationConfigurationService), depends on ServiceA. The Spring Context of the application starts up correctly, when executing it as web server.
However, in tests, running through:
@RunWith(SpringRunner.class) @SpringBootTest(classes = EmmApplication.class) @TestPropertySource("classpath:application-it.properties") @ActiveProfiles("it")
I tried to @SpyBean ServiceB, but it throws this Exception:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'serviceB': Bean with name 'serviceB' has been injected into other beans [serviceA] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
I tried the workaround suggested here of the @Configuration to wrap ServiceA or ServiceB into a mock, but both of them throwed Exceptions of this nature.
I got to work by keeping the @SpyBean in ServiceB and @MockBean in ServiceA, then in @Before:
doAnswer((invocation) -> { serviceB.newApplicationInCatalog(invocation.getArgument(0)); return invocation.getArgument(0); }).when(serviceA).sendNewAppInCatalog(any());In other words, under no circumstances I managed to work with the real bean of ServiceA.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/spring-projects/spring-boot/issues/7033?email_source=notifications&email_token=ADA6A6LMCADK7IQA25DB4RTP4YKMFA5CNFSM4CQ6WYE2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODY2ETJY#issuecomment-506743207, or mute the thread https://github.com/notifications/unsubscribe-auth/ADA6A6O4HEYYKVQDIAJ7ME3P4YKMFANCNFSM4CQ6WYEQ .
Comment From: phillipuniverse
I ran into this trying to validate a @StreamListener was called while also invoking the real underlying functionality. It's the same problem as with a Repository and I solved it with a configurable BeanPostProcessor:
https://gist.github.com/phillipuniverse/4b3d39cdcceb2363a14ebdcc170d9059#file-demoapplicationtests-java-L43-L71
Essentially, instead of @SpyBean you inject it into your test with @Autowired. The ProxiedMockPostProcessor turns it into a mock though so you can still do all of the verify(), return canned responses etc that you would normally do with a Spy.
Comment From: vghero
Even 3 years later it's still a problem :)?
Comment From: philwebb
@vghero I'm afraid it is. If it were an easy fix we'd have probably done it by now.
Comment From: yelhouti
Facing the same issue to test caching, and verify if the cache was used or not...
Comment From: dngzs
My God, I also encountered the same problem. I used the @spybean method, but when I executed the unit test class, I repeatedly created the context. Although it was cached, it was still a waste of performance, and the schema.sql would be Created multiple times and reported an error. Secondly, spy did not get the real mapper for mybaits mapper. I will make mistakes. I am also trying some methods to solve this problem. Do you have any good ways?
Comment From: neerajsu
Here's an example for those looking to solve this problem.
Assume you have a DAO like so, whose customerRepository you want to Spy on
@Repository
public class CustomerDao {
private CustomerRepository customerRepository;
// code below truncated
}
and a repository like so
@Repository
public class CustomerRepository extends JpaRepository<CustomerEntity, String> {
List<CustomerEntity> findByNameIn(List<String> names);
// code below truncated
}
Say, you want to mock findByNameIn
Your test will go as follows
@Autowired
CustomerDao customerDao;
@Autowired
CustomerRepository customerRepository;
@Before
public void setup() {
//this line ensures real methods are called
CustomerRepository customerRepositoryMock = Mockito.mock(CustomerRepository.class, AdditionalAnswers.delegatesTo(customerRepository));
// here I'm mocking findByNameIn method
doAnswer(args -> {
List <String> names = args.getArgument(0, List.class);
return names.stream()
.map(name -> CustomerEntity.builder()
.name(name)
.address("someaddress")
//more stuff here
.build();
).collect(Collectors.toList());
}).when(customerRepositoryMock).findByNameIn(anyList());
//setting the mocked customerRepository into the dao
ReflectionTestUtils.setField(customerDao, "customerRepository", customerRepositoryMock);
}
@Test
public void testSomething {
//write your test as usual
//when your code encounters the method findByNameIn, it will call the mocked method
//when your code encounters other methods, it will call the real methods of CustomerRepository
}
It's that simple.
Comment From: eiswind
I have a strange regression here. After upgrading to 2.3.0.RELEASE none of the above workarounds seem to work.
It can only guess that something has changed in the test context creation.
Let me show you my setup:
@SpringBootTest(webEnvironment =
SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("inmemory")
class RegistrationIntegrationTest {
@TestConfiguration
static class MockRepositoryConfiguration {
@Primary
@Bean
NewsUserRepository newsUserRepositoryMock(NewsUserRepository real) {
return Mockito.mock(NewsUserRepository.class,
AdditionalAnswers.delegatesTo(real));
}
}
@Autowired
NewsUserRepository newsUserRepository;
I also tried a setup with a BeanPostProcessor like in https://gist.github.com/phillipuniverse/4b3d39cdcceb2363a14ebdcc170d9059
In both scenarios the injected NewsUserRepository is not the mocked one, but tha vanilla SimpleJpaRepository. I can see that the mock gets created, but setting a breakpoint in BeforeEach shows something different.
Am I getting something wrong? If not, this likely seems to be a different problem with the test creation.
Comment From: wilkinsona
@eiswind If something's changed in 2.3.0 when you're using Mockito directly (rather than @MockBean or @SpyBean), please open a new issue with a minimal sample that reproduces the problem and we'll take a look.
Comment From: eiswind
@wilkinsona I created some examples and a new isssue. https://github.com/spring-projects/spring-boot/issues/21488
Comment From: wilkinsona
Thanks, @eiswind. To help others facing your problem, I wanted to make a note of what we've learned while looking at #21488.
In short, anyone using the workaround above from @kuhnroyal with Spring Boot 2.3 and the default deferred bootstrapping behaviour may need to use an ObjectProvider when injecting the repository into their test class:
@Autowired
ObjectProvider<MyRepository> repository;
We'll try to take another look at this one and see if we can remove the need for the workaround by getting @SpyBean to work in this scenario.
Comment From: AstralStorm
The workaround still has a bug, in that it does not reset the mock between tests, like spies are.
Instead you need to set it to reset as follows:
@Configuration
public static class MockConfiguration {
@Autowired
private MeetingsRepository meetingsRepositoryReal;
// We need a delegating mock because spy does not work on JPA repositories.
@Primary
@Bean
public MeetingsRepository delegatingMeetingsRepository() {
return mock(MeetingsRepository.class, MockReset.withSettings(MockReset.AFTER)
.defaultAnswer(AdditionalAnswers.delegatesTo(meetingsRepositoryReal)));
}
}
Comment From: onacit
I'm posting another variant of @AstralStorm's workaround.
When the original bean is @Validate Mockito has trouble with Hibernate. And I used a way from this.
return Mockito.mock(MyBeanClass.class,
MockReset.withSettings(MockReset.AFTER)
.withoutAnnotations()
.defaultAnswer(AdditionalAnswers.delegatesTo(myBean)));
Comment From: spc16670
This issue has been chasing milestones for 5 years now...
I have seen that some specific jdk+mockito+spring version combinations appear to somehow work while other throw the UnsatisfiedDependencyExceptions.
If the issue is not going to be fixed anytime soon, could we perhaps publish a list of jdk+mockito+spring version combinations that are known to work?
I can confirm that:
openjdk version "11.0.11-ea" 2021-04-20 + mockito-core 3.6.0 + spring-test 5.3.1 from spring boot 2.4.0 does need a workaround mentioned by @onacit
Comment From: spyro2000
spybean wasn't working for me once, just seems to be null.
Comment From: wilkinsona
We have a fix for this. It's a little bit risky, but not too bad. As such, we've decided that 2.5.x is the best place for it.
Comment From: eiswind
@wilkinsona thx for the fix after all this time. seems to work on my side.
Comment From: wilkinsona
That’s a relief. Thanks for letting us know and for your patience.
Comment From: mikelhamer
sadness
Comment From: nightswimmings
Im in 2.6.4/2.7.0 and
@SpyBean
TestEntityRepository testEntityRepository;
does not get reset across my tests when context is reused. Is easy to spot it because if you do a Mock.reset(testEntityRepository); before using it in the next test class that injects an Spy, everything goes green.
I double checked and ResetMocksTestExecutionListener is being executed after my first test
Is this still expected (i've read there still were some side-effects like this besides the main bug in previous comments)? @wilkinsona
BTW, nice session yesterday @snicoll on the Spring.io ! You caught me staring at you while seeking my mates, from the second floor of the staircase, I felt quite creepy xD
P.S: the bean does show up on ResetMocksTestExecutionListener.resetMocks, but MockReset.get(bean) does not return AFTER, when it should, it returns NONE because SpringBootMockResolver does not find the target object . This does not happen with my other regular service @SpyBean. Ill try to attach a minimum code that reproduces it as soon as possible if you tell me it is unexpected
Comment From: wilkinsona
@nightswimmings This issue was about the creation of the mock or spy, not resetting it. If reseting of a spied Spring Data repository isn't working for you, please open a new issue with a minimal example and we can take a look.
Comment From: nightswimmings
Oks! If it is indeed unexpected, then I'll create the sample on a new issue, thanks!
Comment From: Lucasark
Hi! Same issue on Spring Boot 2.6.x
I create this example:
Using:
@MockBean(name = "serviceName")
Works
But...
@SpyBean(name = "serviceName")
Do not.
For this example I need use Spy to call real methods, and did this to do workaround another depedency limitation.
Comment From: wilkinsona
@Lucasark That doesn't appear to be related to this issue as you are not spying on a Spring Data repository. Also, please note that Spring Boot 2.6 is no longer supported. Please upgrade to the latest Spring Boot 2.7.x release and, if the problem still occurs, open a new issue.
Comment From: pavlo-reshetniak
Looks like the issue is back. For me, the @SpyBean stopped working for Mongo repositories since the spring-boot-starter-parent:3.2.0 version. And everything was fine with the previous version: 3.1.10. I tried to follow the release notes, etc..., but don't see any breaking changes that may cause it.
The use case is trivial. When I'm doing in test:
@SpyBean
private MyFancyRepository<MyFancyType> myFancyRepository;
The exception occurs:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.xxx.repository.MyFancyRepository]: Specified class is an interface
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:77)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1311)
... 39 more
And flipping the version back to 3.1.10 just fixes everything. Should I file a bug for it? :)
Comment From: wilkinsona
@pavlo-reshetniak I'm not sure you have the same problem as the only occurrence of "Specified class is an interface" in this issue is in your comment. If you have a minimal reproducer that you can share, please open a new issue and we'll take a look.
Comment From: pavlo-reshetniak
@wilkinsona, thank you for your reply.
I investigated a bit more and you are right, it's not related to the current thread. The issue is related to the generic type of the MongoRepository bean when initializing it via the @SpyBean. I've submitted a separate ticket with the code example: https://github.com/spring-projects/spring-boot/issues/40234
Regards, Pavlo