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 · 38 comments

Comments

Projects
None yet
@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

This comment has been minimized.

igormukhin commented Oct 10, 2016

Nobody cares 😢 @philwebb @wilkinsona @snicoll

@philwebb

This comment has been minimized.

Member

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

This comment has been minimized.

Member

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

This comment has been minimized.

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

This comment has been minimized.

ianaz commented Oct 25, 2016

+1. Same reason here

@micke239

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

Member

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

This comment has been minimized.

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

This comment has been minimized.

Member

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

This comment has been minimized.

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

This comment has been minimized.

Member

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

Contributor

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

@renannprado

This comment has been minimized.

renannprado commented Apr 13, 2017

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!

@hashpyrit

This comment has been minimized.

hashpyrit commented Apr 13, 2017

Can you show the code that is setting up your mocks and test?

@renannprado

This comment has been minimized.

renannprado commented Apr 13, 2017

@Configuration
public class TestConfiguration {

    @Primary
    @Bean(name = "delegatedMockRepository")
    public MyRepository myMockRepository(final MyRepository myRealRepository) {
        return Mockito.mock(MyRepository.class, AdditionalAnswers.delegatesTo(myRealRepository));
    }
}
@hashpyrit

This comment has been minimized.

hashpyrit commented Apr 13, 2017

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?

@mvamax

This comment has been minimized.

mvamax commented Jun 8, 2017

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

@hashpyrit

This comment has been minimized.

hashpyrit commented Jun 8, 2017

@mvamax

This comment has been minimized.

mvamax commented Jun 9, 2017

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

@hashpyrit

This comment has been minimized.

hashpyrit commented Jun 9, 2017

@mvamax

This comment has been minimized.

mvamax commented Jun 10, 2017

@SpringBootTest

make the mechanism, it checks for all @configuration classes. i verify that spring goes throw the method with a System.out.println.

@hashpyrit

This comment has been minimized.

hashpyrit commented Jun 10, 2017

@mvamax

This comment has been minimized.

mvamax commented Jun 14, 2017

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

@dpinol

This comment has been minimized.

dpinol commented Jun 24, 2017

Hi,
I couldn't make it work with the AdditionalAnswers.delegatesTo trick

  1. 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!"
  2. 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

@hashpyrit

This comment has been minimized.

hashpyrit commented Jun 25, 2017

@philwebb philwebb removed this from the 2.0.0 milestone Nov 8, 2017

@lbernardomaia

This comment has been minimized.

lbernardomaia commented Feb 7, 2018

@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);
}
@odrotbohm

This comment has been minimized.

Member

odrotbohm commented May 30, 2018

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?

@hashpyrit

This comment has been minimized.

hashpyrit commented May 30, 2018

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.

@odrotbohm

This comment has been minimized.

Member

odrotbohm commented May 30, 2018

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/olivergierke/979d42f161123af7d3fca2d7bcc4a335

@odrotbohm

This comment has been minimized.

Member

odrotbohm commented May 30, 2018

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

@hashpyrit

This comment has been minimized.

hashpyrit commented May 30, 2018

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

@odrotbohm

This comment has been minimized.

Member

odrotbohm commented May 30, 2018

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment