spring-boot: Unable to create working mock() for Spring Data Repository

I have a regression upgrading to Spring Boot 2.3.0.RELEASE.

According to https://github.com/spring-projects/spring-boot/issues/7033

I try creating a Mock manually like

@Primary
@Bean
MyRepository testBean(MyRepository real){
        var mock = mock(MyRepository.class,
		AdditionalAnswers.delegatesTo(real));
	when(mock.count()).thenReturn(100L);
	return mock;
}

Calling verify() on that mock fails with Argument passed to verify() is of type $Proxy82 and is not a mock!

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 1
  • Comments: 18 (10 by maintainers)

Most upvoted comments

Thanks, @NicklasWallgren.

@odrotbohm was correct above and it’s happening because you are not configuring the exceptions in the way that is required when AdditionalAnswers.delegatesTo(real) is used. Taken from its javadoc:

This feature suffers from the same drawback as the spy. The mock will call the delegate if you use regular when().then() stubbing style. Since the real implementation is called this might have some side effects. Therefore you should to use the doReturn|Throw|Answer|CallRealMethod stubbing style. Example:

  List listWithDelegate = mock(List.class, AdditionalAnswers.delegatesTo(awesomeList));

  //Impossible: real method is called so listWithDelegate.get(0) throws IndexOutOfBoundsException (the list is yet empty)
  when(listWithDelegate.get(0)).thenReturn("foo");

  //You have to use doReturn() for stubbing
  doReturn("foo").when(listWithDelegate).get(0);

This means that your saveUsingSpy() method should be modified to look like this:

@Test
void saveUsingSpy() {
    doAnswer(returnsFirstArg()).when(repository.getIfAvailable()).save(any(MyEntity.class));

    final MyEntity myEntity = new MyEntity(1L);
    final MyEntity persistedEntity = repository.getIfAvailable().save(myEntity);

    assertEquals(myEntity, persistedEntity);
}

With this change in place, all 4 tests in your sample pass for me.

@odrotbohm @jhoeller using ObjectProvider did the trick!

Could make it work in a single file, can’t believe spring recognizes the repository.

@SpringBootTest
class JpaTest {

	@Entity
	public static class MyEntity{
		@Id
		Long id;
	}


	@TestConfiguration
	static class TestConfig{
		@Primary
		@Bean
		MyRepository testBean(MyRepository real){
			var mock = mock(MyRepository.class,
					AdditionalAnswers.delegatesTo(real));

			return mock;
		}
	}

	@Autowired
	ObjectProvider<MyRepository> repository;

	@Test
	void verify() {
		verify(repository.getIfAvailable(), times(0)).count();
	}

}

interface MyRepository extends JpaRepository<JpaTest.MyEntity,Long>{}

@MockBean and @SpyBean only force the context to be recreated if you do not have a consistent set of mocks and spies across all of your tests. If you have the same @MockBean and @SpyBean configuration in every test class, the tests will all share a single application context.

If you have any further questions, please follow up on Stack Overflow or Gitter. As mentioned in the guidelines for contributing, we prefer to use GitHub issues only for bugs and enhancements.

Just a quick note: the stack trace suggests that the mock is not used as it shows SimpleJpaRepository doing its thing trying to persist a null handed to ….save(…).

@NicklasWallgren Thanks for letting us know. It’s hard to tell what the cause is from the stacktrace above, but on the face of it, I don’t think it’s the same problem as this issue was tracking. It could be another variant of #7033 but I’m not sure that it is. I think the best option at this point is a new issue for now at least. If you’d like us to spend some more time investigating, can you please open one and provide a minimal sample that reproduces the stacktrace you have shared above?

I tried the ObjectProvider trick above, and it worked great, but I have encountered another issue.

It seems like I’m unable to mock CrudRepository::save.

The following mock results in an exception. Mockito.when(repository.getIfAvailable().save(any(MyEntity.class))).then(returnsFirstArg());.

Caused by: java.lang.IllegalArgumentException: Target object must not be null
	at org.springframework.util.Assert.notNull(Assert.java:198)
	at org.springframework.beans.AbstractNestablePropertyAccessor.setWrappedInstance(AbstractNestablePropertyAccessor.java:195)
	at org.springframework.beans.BeanWrapperImpl.setWrappedInstance(BeanWrapperImpl.java:153)
	at org.springframework.beans.AbstractNestablePropertyAccessor.setWrappedInstance(AbstractNestablePropertyAccessor.java:183)
	at org.springframework.beans.AbstractNestablePropertyAccessor.<init>(AbstractNestablePropertyAccessor.java:122)
	at org.springframework.beans.BeanWrapperImpl.<init>(BeanWrapperImpl.java:103)
	at org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper.<init>(DirectFieldAccessFallbackBeanWrapper.java:36)
	at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.getId(JpaMetamodelEntityInformation.java:159)
	at org.springframework.data.repository.core.support.AbstractEntityInformation.isNew(AbstractEntityInformation.java:42)
	at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.isNew(JpaMetamodelEntityInformation.java:246)
	at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:553)
	at jdk.internal.reflect.GeneratedMethodAccessor174.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.springframework.data.repository.core.support.ImplementationInvocationMetadata.invoke(ImplementationInvocationMetadata.java:72)
	at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:382)
	at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:205)
	at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:549)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)