在 Spring Boot 测试中存根 @SpyBean 失败并出现 IllegalArgumentException:聚合实例不得为 null

问题描述 投票:0回答:1

我正在进行一个普通的 Spring Boot 集成测试,它与

@Autowired
@MockBean
配合得很好,一切都是真实的,只是对外界的其余模板进行了嘲笑

@SpringBootTest
@DirtiesContext
@ExtendWith(ConsoleReporterExtension.class)
@ActiveProfiles("it")
@SpringJUnitConfig(Application.class)
@AutoConfigureMockMvc
@AutoConfigureWireMock(port = 0)
@ContextConfiguration(initializers = {TestcontainersInitializer.class}) // for some docker DB
...
@Autowired
private SomeRepository someRepository;

然后我需要模拟持久化过程中的一个问题,并决定监视 bean:

//@Autowired
@SpyBean
private SomeRepository someRepository;

其余的测试仍然工作正常,因此间谍 bean 在应用程序内正确传播,并且因为它没有被存根,所以它的行为与自动连接的一样。 但是当我在

save

期间对其进行存根以抛出一次性异常时
when(someRepository.save(any()))
            .thenThrow(new OptimisticLockingFailureException("some concurrency issue"))
            .thenCallRealMethod();

它在存根上失败了,就好像有人将

null
传递给 save 方法一样,但事实并非如此,特别是我们还没有到达那里,而是它发生在存根定义期间。我缺少什么? (Spring Boot v2.7.10)

java.lang.IllegalArgumentException: Aggregate instance must not be null!

at org.springframework.util.Assert.notNull(Assert.java:201)
at org.springframework.data.jdbc.core.JdbcAggregateTemplate.save(JdbcAggregateTemplate.java:153)
at org.springframework.data.jdbc.repository.support.SimpleJdbcRepository.save(SimpleJdbcRepository.java:78)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:289)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:530)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:286)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:640)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:164)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:139)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:81)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at jdk.proxy2/jdk.proxy2.$Proxy223.save(Unknown Source)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.mockito.internal.util.reflection.ReflectionMemberAccessor.invoke(ReflectionMemberAccessor.java:48)
at org.mockito.internal.stubbing.defaultanswers.ForwardsInvocations.answer(ForwardsInvocations.java:49)
at org.mockito.internal.handler.MockHandlerImpl.handle(MockHandlerImpl.java:110)
at org.mockito.internal.handler.NullResultGuardian.handle(NullResultGuardian.java:29)
at org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:34)
at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:82)
at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:56)
at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor$DispatcherDefaultingToRealMethod.interceptAbstract(MockMethodInterceptor.java:161)
at SomeRepository$MockitoMock$cs2Dymza.save(Unknown Source)
at SomeRepository$MockitoMock$cs2Dymza$$FastClassBySpringCGLIB$$b0e4e380.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)
at SomeRepository$MockitoMock$cs2Dymza$$EnhancerBySpringCGLIB$$6546aaba.save(<generated>)

顺便说一句,该存储库只是一个 Crud 存储库(甚至尝试了一种 hack 来显式重写保存方法,但这显然没有改变任何东西)

@Repository
public interface SomeRepository extends CrudRepository<SomeEntity, Long> {

//<S extends SomeEntity> S save(S entity);
spring-boot spring-boot-test spy spring-data-jdbc
1个回答
1
投票

它在存根上失败了,就好像有人将

null
传递给 save 方法一样,但事实并非如此,特别是我们还没有到达那里,而是它发生在存根定义期间。

事实上,确实如此。您正在调用该方法并在此行中传递

null

when(someRepository.save(any()))

供参考,这是

ArgumentMatchers#any
的实现:

public static <T> T any() {
    reportMatcher(Any.ANY);
    return null; // <- returns null
}

Java 是急切求值的,并且总是在将方法的返回值传递给另一个方法之前调用方法。

when(obj.method())
必须先调用
method()
,然后才能调用
when
。 Mockito 无法改变这个事实。

如果您想避免调用该方法,请使用

doThrow
/
doCallRealMethod
形式:

doThrow(new OptimisticLockingFailureException("some concurrency issue"))
  .doCallRealMethod()
  .when(someRepository)
  .save(any());

更多信息:

© www.soinside.com 2019 - 2024. All rights reserved.