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
Nobody cares 😢 @philwebb @wilkinsona @snicoll
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
@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.
@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.
+1. Same reason here
+1. Same issue for the same reason (wanting to verify interactions). Using spring-data-mongo, though.
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.
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.
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.
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?
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.
Gotcha, good point! 👍 I guess switching to a mock would then break the other tests that really want to persist data, right?
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.
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.
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?
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.
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
.
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));
}
}
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!
Can you show the code that is setting up your mocks and test?
@Configuration
public class TestConfiguration {
@Primary
@Bean(name = "delegatedMockRepository")
public MyRepository myMockRepository(final MyRepository myRealRepository) {
return Mockito.mock(MyRepository.class, AdditionalAnswers.delegatesTo(myRealRepository));
}
}
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?
@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?
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
.
@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?
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
.
@SpringBootTest
make the mechanism, it checks for all @Configuration classes. i verify that spring goes throw the method with a System.out.println.
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"}}}
@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.
Hi,
I couldn't make it work with the AdditionalAnswers.delegatesTo trick
thanks
The source code of your test would help with debugging. Are you certain the mocked bean is being called?
@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);
}
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?
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.
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
@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.
@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.
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.
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.
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(...)
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).
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.
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
.
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.
Even 3 years later it's still a problem :)?
@vghero I'm afraid it is. If it were an easy fix we'd have probably done it by now.
Facing the same issue to test caching, and verify if the cache was used or not...
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?
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.
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.
@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.
@wilkinsona I created some examples and a new isssue.
https://github.com/spring-projects/spring-boot/issues/21488
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.
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)));
}
}
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)));
Most helpful comment
@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.