spring-boot: @SpyBean does not work when used to spy on a Spring Data Repository
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
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Reactions: 53
- Comments: 66 (18 by maintainers)
Commits related to this issue
- Support spying on final classes Closes gh-7033 — committed to cvienot/spring-boot by deleted user 5 years ago
- Support spying on final classes Closes gh-7033 — committed to cvienot/spring-boot by deleted user 5 years ago
- Support spying on final classes Closes gh-7033 — committed to cvienot/spring-boot by deleted user 5 years ago
- Allow @SpyBean to be used to spy on a Spring Data repository Fixes gh-7033 — committed to spring-projects/spring-boot by wilkinsona 3 years ago
- remove hack by reason https://github.com/spring-projects/spring-boot/issues/7033 — committed to MFomichev/spring-data-sandbox by MFomichev 3 years ago
@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.
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.
+1. Same issue for the same reason (wanting to verify interactions). Using spring-data-mongo, though.
I followed @hashpyrit’s advice, using a special configuration for tests. It works well and is completely decoupled from the test cases.
We have a fix for this. It’s a little bit risky, but not too bad. As such, we’ve decided that 2.5.x is the best place for it.
I encountered this problem as well. I got around it by creating my mock as follows:
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.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:
+1. Same reason here
Nobody cares 😢 @philwebb @wilkinsona @snicoll
Hi there. Another use case could be to use
@SpyBeanon a repository to test that a service with@Cacheableannotation will call the repository once and the second time the value is returned from the cache.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
ObjectProviderwhen injecting the repository into their test class:We’ll try to take another look at this one and see if we can remove the need for the workaround by getting
@SpyBeanto work in this scenario.I’m posting another variant of @AstralStorm’s workaround.
When the original bean is
@ValidateMockito has trouble with Hibernate. And I used a way from this.Even 3 years later it’s still a problem 😃?
If you look at what
Mockito.spy(…)does, it’s essentially aMockito.mock(…)but with a default answer of calling the real method. So it’s sort of the same I was achieving with myBeanPostProcessor. It looks like you can avoid the real methods being called by using thedoReturn(…).when(spy).methodOnSpy()style of stubbing as documented inMockito.spy(…).Let’s see what the others say how to proceed.
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.
This issue has been chasing milestones for 5 years now…
I have seen that some specific jdk+mockito+spring version combinations appear to somehow work while other throw the UnsatisfiedDependencyExceptions.
If the issue is not going to be fixed anytime soon, could we perhaps publish a list of jdk+mockito+spring version combinations that are known to work?
I can confirm that:
openjdk version “11.0.11-ea” 2021-04-20 + mockito-core 3.6.0 + spring-test 5.3.1 from spring boot 2.4.0 does need a workaround mentioned by @onacit
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:
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.
Any update on progress made? Ideally I would just like to add
@SpyBeanin front of my repository in the test and just have it work (rather than dealing with workarounds).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.