Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@SpyBean on Data Jpa Repository bean isn't working #7033

Open
igormukhin opened this issue Sep 27, 2016 · 56 comments
Open

@SpyBean on Data Jpa Repository bean isn't working #7033

igormukhin opened this issue Sep 27, 2016 · 56 comments
Milestone

Comments

@igormukhin
Copy link

@igormukhin igormukhin commented Sep 27, 2016

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

@igormukhin
Copy link
Author

@igormukhin igormukhin commented Oct 10, 2016

Nobody cares 😢 @philwebb @wilkinsona @snicoll

@philwebb
Copy link
Member

@philwebb philwebb commented Oct 11, 2016

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
@philwebb philwebb added this to the 1.4.2 milestone Oct 11, 2016
@philwebb
Copy link
Member

@philwebb philwebb commented Oct 11, 2016

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

@igormukhin
Copy link
Author

@igormukhin igormukhin commented Oct 11, 2016

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

@ianaz
Copy link

@ianaz ianaz commented Oct 25, 2016

+1. Same reason here

@micke239
Copy link

@micke239 micke239 commented Nov 3, 2016

+1. Same issue for the same reason (wanting to verify interactions). Using spring-data-mongo, though.

@snicoll snicoll modified the milestones: 1.4.2, 1.4.3 Nov 8, 2016
@hashpyrit
Copy link

@hashpyrit hashpyrit commented Nov 16, 2016

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.

@odrotbohm
Copy link
Member

@odrotbohm odrotbohm commented Dec 19, 2016

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.

@hashpyrit
Copy link

@hashpyrit hashpyrit commented Dec 19, 2016

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.

@odrotbohm
Copy link
Member

@odrotbohm odrotbohm commented Dec 19, 2016

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?

@hashpyrit
Copy link

@hashpyrit hashpyrit commented Dec 19, 2016

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.

@odrotbohm
Copy link
Member

@odrotbohm odrotbohm commented Dec 19, 2016

Gotcha, good point! 👍 I guess switching to a mock would then break the other tests that really want to persist data, right?

@hashpyrit
Copy link

@hashpyrit hashpyrit commented Dec 19, 2016

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.

@hashpyrit
Copy link

@hashpyrit hashpyrit commented Dec 19, 2016

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.

@philwebb philwebb modified the milestones: 1.4.4, 1.4.3 Dec 20, 2016
@robertotru
Copy link

@robertotru robertotru commented Dec 22, 2016

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?

@hashpyrit
Copy link

@hashpyrit hashpyrit commented Dec 22, 2016

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.

@robertotru
Copy link

@robertotru robertotru commented Dec 22, 2016

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.

@kuhnroyal
Copy link

@kuhnroyal kuhnroyal commented Dec 30, 2016

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));
    }
}
@philwebb philwebb modified the milestones: 2.0.0, 1.4.4 Jan 18, 2017
cvienot pushed a commit to cvienot/spring-boot that referenced this issue Apr 26, 2019
cvienot pushed a commit to cvienot/spring-boot that referenced this issue Apr 26, 2019
cvienot pushed a commit to cvienot/spring-boot that referenced this issue Apr 26, 2019
@vab2048
Copy link

@vab2048 vab2048 commented Jun 5, 2019

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

@leandrodelsole
Copy link

@leandrodelsole leandrodelsole commented Jun 28, 2019

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.

@hashpyrit
Copy link

@hashpyrit hashpyrit commented Jun 28, 2019

@phillipuniverse
Copy link
Contributor

@phillipuniverse phillipuniverse commented Aug 8, 2019

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.

@vghero

This comment has been hidden.

@philwebb

This comment has been hidden.

@yelhouti
Copy link

@yelhouti yelhouti commented Mar 2, 2020

Facing the same issue to test caching, and verify if the cache was used or not...

@dngzs
Copy link

@dngzs dngzs commented Mar 18, 2020

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?

@neerajsu
Copy link

@neerajsu neerajsu commented Apr 30, 2020

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.

@eiswind
Copy link

@eiswind eiswind commented May 18, 2020

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.

Bildschirmfoto von 2020-05-18 11-42-53

Am I getting something wrong? If not, this likely seems to be a different problem with the test creation.

@wilkinsona
Copy link
Member

@wilkinsona wilkinsona commented May 18, 2020

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

@eiswind
Copy link

@eiswind eiswind commented May 18, 2020

@wilkinsona I created some examples and a new isssue.
#21488

@wilkinsona
Copy link
Member

@wilkinsona wilkinsona commented May 19, 2020

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.

@wilkinsona wilkinsona self-assigned this May 19, 2020
@wilkinsona wilkinsona modified the milestones: General Backlog, 2.4.x May 19, 2020
@AstralStorm
Copy link

@AstralStorm AstralStorm commented Sep 15, 2020

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)));
        }
    }
@philwebb philwebb modified the milestones: 2.4.x, 2.5.x Oct 19, 2020
@onacit
Copy link

@onacit onacit commented Dec 18, 2020

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)));
@wilkinsona wilkinsona removed their assignment Jan 12, 2021
@philwebb philwebb modified the milestones: 2.5.x, 2.6.x Mar 19, 2021
@spc16670
Copy link

@spc16670 spc16670 commented Apr 17, 2021

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.